객체지향

최찬호·2023년 3월 25일
0

객체지향

  • 클래스: 연관성이 높고 잘 정의된 기능을 공유하는 데이터와 루틴의 모음이다. 또한 공통 데이터가 없더라도 관련 서비스를 제공하는 루틴의 집합이 될 수도 있다.

  • 객체: 런타임중에 프로그램에 존재하는 구체적인 엔티티다. 프로그램을 실행할 때 생성되는 특정한 값과 속성을 갖는 동적인 것이다.

추상화

구현 세부사항을 무시할 수 있는 모델을 제공함으로써 복잡성 관리에 도움을 준다.
캡슐화를 통해 드러난 (일반적인 부분)인터페이스를 통해 상호작용

캡슐화

복잡한 개념을 더욱 단순한 관점으로 보게해서 복잡한 개념의 세부 사항을 못 보게 한다.세부 사항을 알고 싶어 할 때조차 이를 원천적으로 봉쇄해버리는 강력한 방법

  • 데이터와 그 데이터에 작용하는 메소드를 하나로 묶음
  • 객체 안에 있는 데이터(전부 혹은 일부)를 외부로부터 보호 : 정보은닉
    #외부: 다른 객체

상속

  • 이미 존재하는 클래스를 기반으로 확장된 클래스를 만드는 방법
  • 확장된 클래스는 기존의 클래스에 속한 데이터, 메소드를 모두 물려받음
  • 코드 중복을 막을 수 있다

한 클래스가 다른 클래스의 특별한 형태라는 개념이다. 상속의 목적은 두 개 이상의 파생 클래스에서 공통으로 사용되는 요소를 갖는 기본 클래스를 정의하여 더 간단한 코드를 작성하는 데 있다.공통요소는 루틴 인터페이스나 구현부, 데이터 멤버, 데이터형이 될 수 있다 개발자가 기존 클래스를 상속하여 새로운 클래스를 작성하기로 결정할 때 새로운 클래스는 기존 클래스의 특수화된 버전이다. 파생 클래스가 기본 클래스에 정의된 인터페이스 계약을 완벽하게 따르지 않는다면 상속은 올바른 구현 기법이 아니다. 상속은 프로그램을 복잡하게 만들기 때문에 위험한 기법이다. 공통으로 사용되는 인터페이스와 데이터, 행위를 상속단계에서 가능한 한 가장 높은 곳으로 옮겨라.

다형성

OOP의 핵심이라 여기는 특징
  • 동일한 인터페이스의 메소드를 호출 시 동작을 달리하는 것 (메소드의 의미는 동일)
  • late binding 어떤 구체적인 메소드가 호출 될지는 런타임시 결정
  • 상속은 다형성의 선수조건

정보은닉

구조적 설계와 객체지향 설계 모두에 있어 기본적인 부분이다.객체지향 설계에서는 정보은닉을 위해 캡슐화와 모듈성 개념이 생겼고 추상화개념과 연결된다. 정보은닉은 "비밀"이라는 개념을 특징으로 하는데, 그 비밀이란 소프트웨어 개발자가 설계나 구현에서 나머지 프로그램으로부터 한 부분을 숨기기로 결정하는 것이다. "정보 은닉이 재작업을 없애는 강력한 기법이라고 밝혔으며 점증적이고 변화가 많은 환경에서 특히 효과적이라고 지적했다"(Boehm 1987)
정보은닉은 복잡성을 감추는 데 중점을 두고 있기 때문에 소프트웨어의 주요 기술적 의무(복잡성 관리)에 특히 강력한 발견적 기법이다. 클래스에 대한 인터페이스는 가능한 한 내부 작업을 드러내지 않아야 한다.
구현세부사항과 타입을 숨겨한다.
정보 은닉에서의 비밀은 다음과 같은 두 개의 일반적인 그룹으로 나뉜다.

  • 특별하게 관심이 없는 경우에 고민할 필요가 없도록 복잡성을 감추는 것
  • 변경이 발생했을 때 그 효과가 일부에만 영향을 미치도록 변경의 원인을 감추는 것 #복잡한 데이터 타입, 파일 구조, 불린 테스트, 알고리즘 등이 복잡성의 원인이다.

객체설계단계

  • 객체와 객체의 속성(메소드와 데이터)을 식별한다.
  • 각 객체에 무엇을 할 수 있는지 결정한다.
  • 각 객체가 다른 객체에 무엇을 할 수 있는지 결정한다.
  • 각 객체에서 다른 객체에 보일 부분을 결정한다. 즉, 공개되는 부분과 비공개되는 부분을 결정한다.
  • 각 객체의 공개 인터페이스를 정의한다.

변경될 것 같은 영역을 찾아라

변경의 효과가 한 루틴이나 클래스, 패키지에 제한되도록 불안정한 영역을 고립시키는 것이 목표다.

  1. 변경될 것처럼 보이는 항목을 찾는다
  • 요구사항 개발을 잘했다면 그 안에 변경 가능성이 있는 것의 목록과 각 변경이 일어날 확률을 담고 있을 것이다. 그런 경우에는 변경 가능성이 있는 것을 파악하기가 쉽다.
  1. 변경될 것 같은 항목을 분류한다.
  • 1단계에서 찾은 변경되기 쉬운 요소를 고유한 클래스로 분류하거나 함께 변경될 수 있는 다른 요소들과 함께 고유한 클래스로 분류한다.
  1. 변경될 것처럼 보이는 항목을 고립시킨다.
  • 잠재적인 변경에 영향을 받지 않는 상호 클래스 인터페이스를 설계한다. 그리고 그러한 변화가 해당 클래스의 내부에 제한되어 외부는 영향을 받지 않도록 인터페이스를 설계한다. 변경된 클래스를 사용하는 클래스는 변경이 발생했다는 사실을 몰라야 한다. 클래스의 인터페이스가 자신의 비밀을 보호해야한다.

    결합을 느슨하게 유지하라

    결합은 클래스나 루틴이 다른 클래스나 루틴과 얼마나 밀접하게 연관되어 있는지를 기술한다. 목표는 다른 클래스나 루틴과 작고 직접적이며 눈에 띄고 유연한 관계를 갖는 클래스와 루틴을 생성하는 것으로,이를 "느슨한 결합(loose coupling)"이라고 한다.

    하나의 클래스나 루틴이 다른 클래스나 루틴에 의해 쉽게 사용될 수 있을 정도로 느슨한 상태가 훌륭한 결합이다.

SOLID

리스코프치환원칙

파생 클래스가 기본 클래스의 특수화된 버전("is a")이 아니라면 기본 클래스로부터 상속받아서는 안된다. "서브클래스는 사용자가 그 차이점을 모른 채 기본 클래스의 인터페이스를 통해 사용할 수 있어야 한다."
기본 클래스에 정의된 모든 루틴은 파생 클래스에서 사용될 때도 의미가 같아야 한다.

결합의 기준 #모듈 = 클래스 or 루틴

크기

크기는 모듈 사이의 연결 횟수를 의미한다. 결합에서는 작은 인터페이스를 갖는 모듈이 다른 모듈에 연결하기가 상대적으로 쉽기 때문에 가능하면 작은 것이 좋다.매개변수를 하나만 받는 루틴이 여섯 개의 매개변수를 받는 루틴보다 그것을 호출하는 모듈에 느슨하게 결합되어 있다. 잘 정의된 네 개의 공개 메서드를 갖는 클래스는 37개의 공개 메소드를 노출한 클래스보다 그 클래스를 사용하는 모듈에 느슨하게 결합되어 있다.

가시성

가시성은 두 모듈 간의 연결이 얼마나 명시적인지를 의미한다. 프로그래밍은 미국 중앙정보국에서 근무하는 것과는 다르다. 다시 말해 남을 잘 속인다고 인정해 주지 않는다. 오히려 광고와 더 비슷하다. 가능한 한 연결부분을 눈에띄게 만들어야 많은 명성을 얻을 수 있다. 매개변수 목록에 데이터를 전달하는 것은 분명하게 연결하는 것이디 때문에 좋다.다른 모듈이 데이터를 사용할 수 있도록 전역 데이터를 수정하는 것은 교묘한 연결이기 때문에 나쁘다.
유연성: 유연성은 얼마나 쉽게 모듈 사이의 연결을 변경할 수 있는지를 의미한다. 이상 적으로는 피복전선과 인두 같은 것보다는 컴퓨터에 있는 USB 연결 장치 같은 것을 원할 것이다. 유연성은 다른 결합 특징의 산물을 어느정도가지고 있지만, 다른 점도 약간 있다. 한마디로 말하자면 어떤 모듈이 다른 모듈을 호출하는 게 쉬울수록 결합은 더 느슨해지는데, 그렇게 하면 유연성도 커지고 유지보수하기도 쉽기 때문에 좋다.시스템 구조를 만들 때는 상호 연결을 최소화하도록 프로그램을 나눠라.프로그램이 나무토막 하나라면 나무를 깨알같이 쪼개도록 한다.

결합의 종류

간단한 데이터 매개변수 결합 두 모듈 사이에서 전달되는 모든 데이터가 기본 데이터형이고 모든 데이터가 매개변수로 전달된다면 두 모듈은 단순-데이터-매개변수로 결합된 것이다. 이러한 종류의 결합은 자연스럽고 허용할 수 있다.

간단한 객체 결합 모듈이 객체를 인스턴스화한다면 그 모듈은 객체에 단순-객체로 결합된 것이다.이러한 종류의 결합은 좋다.

객체 매개변수 결합 Object1이 Object2에게 Object3을 넘겨달라고 요구한다면 두 모듈은 서로에 대해서 객체-매개변수로 결합한 것이다. 이러한 종류의 결합은 Object2가 Object3에 대해서 알아야 하기 때문에 Object1이 Object2에게 기본 데이터형을 넘기라고 하는 것보다 결합이 좀 더 강하다.

의미론적인 결합 한 모듈이 다른 모듈의 프로그래밍 요소를 이용하지 않고 다른 모듈의 내부 작동에 대한 논리적인 지식을 사용할 때 가장 발견하기 어려운 종류의 결합이 발생한다.

  • Module1이 Module2에게 무엇을 할지 지시하기 위해서 Module2에 제어 플르개를 전달한다.이 접근 방법에서는 Module1이 Module2의 내부 작동 즉,Module2가 전달받은 제어 플래그로 무엇을 할지 반드시 알아야 한다. Module2가 제어 플래그에 대해 특정 데이터형을 정의한다면(열거형이나 객체)이러한 방법을 사용해도 문제가 없을 것이다.
  • Module1이 전역 데이터를 수정한 후에 Module2가 그 전역 데이터를 사용한다. 이러한 접근 방법은 Module2 입장에서 Module1이 Module2가 원하는 방법대로 전역 데이터를 수정했고 Module1이 적절한 시간에 호출되었다는 가정이 필요하다.
  • Module1의 인터페이스는 Module1.routine()이 호출되기 전에 Module1.init()루틴이 호출되어야 한다는 것을 말해주고 있다. Module2는 Module1.routine()가 Module1.init()을 호출한다는 것을 알고 있기 때문에 Module1의 인스턴스를 만든다음 Module1.init()을 먼저 호출하지 않고 곧바로 Module1.routine()을 호출한다.
  • Module1이 Object를 Module2에 전달한다. Module1은 Module2가 Object의 7개 메서드 중에서 세개만 사용한다는 것을 알고 있기 때문에 Object에서 세 개의 메서드에 필요한 데이터만 부분적으로 초기화한다.
  • Module1이 BaseObject를 Module2에 전달한다. Module2는 Module1이 실제로는 DerivedObejct를 전달한다는 것을 알고 있기 때문에 BaseObject를 DerivedObject로 변환하여 DerivedObject의 메서드를 호출한다.

효과적인 모듈이 추가적인 추상화 수준을 제공하는 것이 느슨한 결합의 핵심이다. 전체적인 프로그램의 복잡성을 줄이고 한 번에 한 가지만 집중할 수 있게 한다. 하지만 어떤 모듈을 사용하기 위해서 한 번에 하나 이상의 것(내부 작업에 대한 이해, 전역 데이터의 수정, 불확실한 기능)에 집중해야 한다면 추상적인 효과는 사라지고 복잡성을 관리하는 데 도움을 주는 모듈의 능력은 줄어들거나 사라져버린다.

클래스와 루틴은 복잡성을 줄이기 위해서 가장 먼저 사용하는 지능적인 도구다. 클래스와 루틴이 일을 더 단순하게 만들고 있지 않다면 제 역활을 못 하고 있는 것이다.

응집력을 강하게 하라

응집력은 구조적인 설계의 결과로 얻을 수 있고 대개 결합과 같은 맥락으로 다룬다.응집력은 클래스에 있는 모든 루틴이나 루틴에 있는 모든 코드가 얼마나 밀접하게 중심 목적을 지원하고 있는지, 그 클래스가 얼마나 집중되어 있는지를 나타낸다. 매우 연관성이 높은 기능을 포함하는 클래스를 응집력이 강하다고 말하며, 발견적 학습의 목표는 가능한 한 응집력을 강하게 만드는 것이다. 응집력은 복잡성 관리에 유용한 도구이다. 클래스의 코드가 중심 목표를 더 많이 지원할수록 코드가 수행하는 모든 것을 더 쉽게 기억할 수 있기 때문이다.

계층을 만들어라

계층은 단계식 정보 구조로, 가장 일반적이거나 추상적인 항목이 최상위에 위치하고 점차 상세하고 구체적인 항목이 낮은 수준에 위치한다.

모듈화를 유지하라

모듈화의 목표는 각 루틴이나 클래스를 "블랙박스"처럼 만드는 것이다. 무엇이 들어가고 나오는지 알지만, 안에서 무슨 일이 일어나는지는 알지 못한다. 블랙박스는 단순한 인터페이스와 잘 정의된 기능성 덕분에 어떤 입력이 들어와도 그에 따른 출력을 정확하게 예상할 수 있다.

개인적인 생각(정리)

객체지향이란?

하나하나의 데이터를 논리적, 의미론적으로 관련이 있는 것들 끼리 묶어 집합으로 생각을 하는 방식이라고 생각합니다. 저수준 데이터 타입, 개발자가 모든 데이터의 연관성을 고려하는 것 보다 집합으로 생각할 수 있게 되면서 실수를 방지할 수 있다고 생각합니다.


이렇게 연관있는 데이터끼리 묶는 작업의 결과물을 클래스라고 하는데 이 클래스를 만드는 과정 또는 만들어진 클래스를 확인작업/변경을 결정하는 캡슐화, 상속, 추상화, 다형성 4가지 특성이 존재합니다.


캡슐화란 서로 관련있는 데이터, 메소드를 하나로 묶는 것을 의미합니다. 이 과정에서 클래스가 다른 클래스에게 어떤 부분을 드러내고 감출지를 결정합니다. 서로 다른 서비스에서 같은 이름의 클래스가 존재한다면 비즈니스규칙, 서비스의 성격등 필요로 하는 정보에 따라 드러내고 감추는 부분이 달라질 수 있습니다.


추상화는 구체적인 것을 일반화시키는 것으로 저수준데이터를 묶어 클래스를 묶는 과정 또는 클래스와 클래스를 일반화해서 다형성을 이용할 수 도 있습니다. 또한 사용하는 객체가 정확히 어떤 데이터가 있는지 알 필요가 없게한다는 장점도 있습니다. 이 말은 개발자가 객체를 사용함에 있어 객체의 모든 정보를 알지 못해도 공개 인터페이스를 통해 이해하고 사용할 수 있다는 뜻이며 그렇게 작성해야 한다는 의미라고 생각합니다. 그러나 추상화를 통해 구체적인 특징이 사라지므로 이해하기 힘든 경우가 생길 수 있다고 생각합니다.


상속은 존재하는 클래스를 기반으로 확장된 클래스를 만드는 방법입니다. 부모클래스의 필드와 메소드를 모두 물려받으며 물려받은 메소드를 재정의(overriding)하여 구현을 재정의하여 다형성을 활용할 수 있거나 부모클래스에는 존재하지 않는 자신만의 메소드를 가질 수 있습니다. 중요한 것은 확장이란 부모클래스의 모든 스펙을 물려받는 것이므로 자식클래스도 부모 클래스와 동일한 의미의 클래스여야 합니다. 그러니까 자식클래스는 추가한 자신만의 공개인터페이스와 부모클래스로 부터 물려받은 공개인터페이스가 있을 텐데 공통된 인터페이스의 메소드를 통해 다른 객체와 상호작용시 부모와 자식의 메소드의 의미가 동일해야 합니다.


다형성은 동일한 시그니처의 메소드를 호출 시 객체에 따라 동작을 달리(세부구현사항이 다름)하는 것 입니다. 상속을 통해 이용할 수 있으며 세부구현사항이 각 클래스에 구현되어 있기에 캡슐화를 따르고 변경시 구현이 필요한 새로운 클래스를 생성하거나 특정 클래스의 구현을 변경해주면 되기에 유지보수성이 높아지는 장점이 있습니다.

profile
체득하고 이해하자

0개의 댓글