‘객체 지향에 관한 오해와 진실’ 편을 잇는 코드 작성에 관한 새로운 지침. 제대로 된 객체 지향과 잘못된 설계에 관해 지적하고 이를 해결하는 지침을 제시한다.
-
객체지향
- 프로그래밍에서 필요한 데이터를 추상화시켜 상태와 행위를 가진 객체를 만들고 그 객체들 간의 유기적인 상호 작용을 통해 로직을 구성하는 프로그래밍 방법.
- 기본 용어 정리
- 클래스 - 집단(type)에 속하는 속성(attribute)과 행위(behavior)를 변수와 메서드로 정의한 것
- 객체
- 클래스가 실제 메모리상에 할당된 것으로 상태(변수), 행위에 대한 책임를 가진다.
- 추상화 - 공통의 속성이나 기능을 묶어 이름을 붙이는 것
- 캡슐화 - 불필요한 정보는 숨기고 중요한 정보( 속성만 말하는 것이 아님 )만을 표현함
- 다형성 - 객체가 상황에 따라 다른 의미로 해석될 수 있는 것
- 패키지(Module) - 클래스 집합을 하나의 논리적 단위로 묶는 구성 요소
- 역할 , 책임 , 협력 = 책임(메소드)이 모여 역할(객체)가 되고 역할 간의 협력(메시지 전송)이 시스템이 된다.
-
클래스
- 데이터와 메소드가 결합된 사용자 정의 타입(User-Defined Type)
- 추상 데이터 타입인 클래스는 객체 지향에서 큰 부분을 차지 하고 있다. 이러한 클래스의 책임이 넘치는 범위인가 아니면 좁은 것인가에 대한 판단을 실질적으로 할 수 있는 것은 개발자 밖에는 없다.
- 설계 가이드
- 클래스의 기준은 초기화 데이터를 기준으로 삼으면 유용하다.
- 생성자에 NULL이 필요하다면 잘못된 설계 일 가능성이 높다.
- 클래스가 변경되는 이유는 단 한 가지여야 한다.( SRP 원칙)
-
객체
- 인간이 분명하게 인지하고 구별할 수 있는 물리, 개념적인 경계를 지닌 것
- 클래스의 인스턴스인 객체는 상태( 변수 ) 와 내부 함수 (private ) , 외부 함수( public ) 으로 구분 할 수 있다.
- 클래스의 책임과 일정 부분 겹칠 수 있으나 객체는 상태를 가지게 될 것이며, 어떠한 객체와 통신 할 지는 런타임에 결정되기 때문에 다르다고 볼 수 있다.
- 책임의 명확성 가이드
- 객체가 상태에 초점을 맞춘 데이터 중심 설계 인지 고민한다.
- 객체 자신의 문제는 스스로 해결 할 수 있어야 한다.
- 객체의 행동을 기반으로 타입 계층을 구성하라. ( 코드의 재 사용이 목적이면 안된다. )
- 현실 세계의 도메인에서 사용되는 이름을 객체에 부여하라.
-
메세지( 메서드 )
- 함수는 객체가 다른 객체와 통신하는 유일한 방법이면서 ( 캡슐화가 잘 되어있다면 ) 자신의 책임을 명확하게 드러내는 역할을 한다.
- 함수 리시버는 적절한 메서드를 선택하여 메시지에 대한 응답을 제공한다.
- 메시지는 수신자, 메시지 이름, 인자의 조합으로 구성된다.
- 메시지 작성 가이드
- 메서드 명은 클라이언트가 이해 가능 하도록 작성한다.
- 결과와 목적만을 포함하는 시그니쳐를 작성하도록 노력하라
- 오직 인접한(관련성 있는) 객체와만 통신하라 ( 디미터의 법칙 ) → 여러 연관성이 있는 객체는 다양한 변경의 이유를 가지게 된다.
- 쿼리와 명령의 기능을 동시에 하는 함수를 작성하지 마라. ( 명령-쿼리 분리 원칙 )
- 프로시져(쿼리) : 부수 효과(상태 변경)가 발생하지만 값을 반환하지는 않는다.
- 함수(명령) : 부수 효과가 발생하지 않지만 값을 반환한다
-
객체 지향의 중요 3요소
- 캡슐화
- 객체 지향에서 중요한 요소 중 하나인 캡슐화는 일반적으로 데이터 은닉만을 생각하는 경우가 많다.
- 이는 캡슐화의 일부일 뿐이며 진정한 캡슐화는 사용되는 객체에 대한 정보 자체의 은닉화를 의미한다. → 안정적인 인터페이스( Public Method ) 뒤로 구현(객체)을 감추어라.
- 변하는 기능은 추상화 하고 고정 개념으로부터 분리하라.
- 변하는 부분은 캡슐화 하고 상속 계층을 생성하라.
- 변하지 않는 부분에서 변하는 부분을 참조하여 이용하라. → like 템플릿 메서드 패턴
- 추상화
- 복잡한 자료, 모듈, 시스템 등으로부터 핵심적인 개념 또는 기능을 간추려 내는 것을 말한다.
- 목적에 집중하면서 다른 부수적인 것들을 감추는 역할을 한다.
- 단순히 추상화는 Abstract Class라고 여기는 것은 착각이다.
- 추상화 방법
- 타입 추상화 : 데이터 중심의 추상화 . 우리가 흔히 알고있는 Abstract Class를 이용한 방법이다. 이는 새로운 오퍼레이션(기능) 을 추가 할 시 에 매우 유용하다.
- 메시지 추상화 : 객체 지향적인 추상화. 우리가 흔히 알고 있는 interface를 이용한 방법이다. 이는 새로운 추상데이터 타입을 추가 시에 매우 유용하다.
- 다형성
- 동일한 요청에 대해 서로 다른 방식으로 응답 할 수 있는 능력.
- 클라이언트 객체에서 추상 인터페이스(메시지)를 이용하여 여러 객체를 이용하여 메시지를 수신하는 것.
- 하나의 코드로 다양한 객체를 연결하여 쓸 수 있음을 표현한다.
- 다형성의 종류
- 메서드 오버로딩
- 강제 다형성 : 연산자 오버로딩
- 매개변수(제네릭) 다형성
- 포함 다형성 ( 서브 타이핑 )
-
상속
- 상속은 타입 계층을 구성하는 가장 쉬운 방법으로 부모 클래스의 모든 속성과 메시지를 자식에게 물려 주거나( 그래서 이름이 상속이 아닐까 ), 책임에 대한 구현( Abstract Class )을 강제한다
- 코드의 재 사용성을 높이는 대가로써 매우 높은 의존성을 가지게 하는 트레이드 오프
- 종류
- 서브 클래싱 : 코드의 재 사용성이 목적 ( 구현 상속 )
- 서브 타이핑 : 인터페이스 상속이 목적
- 서브 클래싱이 주로 상속에서 문제로써 제기되며 그 이유는 아래와 같다
- 불필요한 인터페이스의 상속
- 메소드 오버라이딩의 부작용 존재
- 부모,자식 클래스의 동시 수정문제
- 상속의 단점
- 상속된 자식 클래스가 Super 메서드의 호출이 결합도를 높이므로 주의하라
- 부모 메서드의 오버라이딩은 부모와 자식의 결합도을 강화한다.
- 다중 상속이 불가능하다.
- 상속을 써야 할 때
- IS-A 관계 일때 : “[자식]은 [부모]다” 라는 말이 성립될 때
- 클라이언트 입장에서 부모 타입으로 대체 가능할 때 → 즉, LSP가 가능해야 한다
- 자식 클래스의 추상메서드가 존재한다면 부모 클래스로 이동이 가능 할 때
-
믹스인
- 상속에서 보이는 몇 가지 단점을 해결하기 위해 등장한 방법 중 하나
- 코드 조각의 일부를 클래스에 주입하여 사용하는 방법으로 표현
- 기본적으로 인터페이스 Default Method를 이용하여 구현하거나 위임을 이용한다.
- 컴파일 타입에 객체의 조합이 이루어지기 때문에 의존성은 상속과 유사하다고 볼 수 있으나 다양한 타입 계층을 표현하기에 유리하다는 장점이 있다.
- 코드 예제
interface A{ fun a(){} } class AA : A interface B { fun b(){} fun c(){} } class BB : B class C : B by BB(), A by AA()
- 위의 코드를 보게 되면, 클래스 C는 A와 B 라는 타입 계층을 BB(), CC() 객체를 통해 컴파일 타입에 조합하여 사용하면서도 그 구현을 C 클래스가 하지 않게 된다.
-
합성
-
공변
-
의존성
-
깨알팁!!
- 타입 : 유효한 오퍼레이션의 집합, 미리 약속된 문맥( 함수 시그니처 ) 제공. 즉 개념적인 선언