- 객체지향은 말 그대로 객체를 지향하는 것이다.
어떤 클래스가 필요한지를 고민하기 전에 어떤 객체들이 필요한지 고민하라. 어떤 객체들이 어떤 상태와 행동을 가지는지를 먼저 결정해야 한다.
객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 일원으로 봐야 한다. 객체들의 모양과 윤곽이 잡히면 공통된 특성과 상태를 가진 객체들을 타입으로 분류하고 이 타입을 기반으로 클래스를 구현하라.
- 도메인: 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야
- 클래스의 구조는 도메인의 구조와 유사항 형태를 띠어야 한다.
인스턴스 뱐수의 가시성은 private, 메서드의 가시성은 public 클래스를 구현하거나 사용 시 가장 중요한 것은 클래스 경계를 구분 짓는 것 클래스의 내부와 외부를 구분해야하는 이유? 경계의 명확성이 객체의 자율성을 보장하기 때문, 프로그래머에게 구현의 자유를 제공하기 때문.
- 자율적인 객체
- 객체는 상태와 행도을 함께 가지는 복합적 존재
- 스스로 판단하고 행동하는 자율적인 존재
- 인터페이스와 구현의 분리 원칙은 훌륭한 객체지향 프로그램의 핵심 원칙
- 퍼블릭 인터페이스: 외부에서 접근 가능한 부분
- 구현: 오직 내부에서만 접근 가능한 부분
- 일반적으로 객체의 상태는 숨기고 행동만 외부에 공개해야 한다
- 프로그래머의 자유
- 구현 은닉
- 클래스 작성자는 클라이언트 프로그래머에게 필요한 부분만 공개하고 나머지는 꽁꽁 숨겨야 한다.
- 접근 제어
- 설계가 필요한 이유는 변경을 관리하기 위해서라는 것
- 객체의 변경을 관리할 수 있는 기법 중 가장 대표적인 것이 접근 제어
- 변경될 가능성이 있는 구현 내용을 private 영역 안으로 감춤으로써 변경으로 인한 혼란을 최소화
- 구현 은닉
객체들 사이에 이뤄지는 상호작용 - 협력
public class Movie {
...
public Money calculateMovieFee(Screening screening) {
return fee.minus(discountPolicy.calculateDiscountAmount(screening);
}
}
discountPolicy에게 메시지를 전송하고 있다.
생성자의 파라미터 목록을 이용해 초기화에 필요한 정보를 전달하도록 강제하면 올바른 상태를 가진 객체의 생성을 보장할 수 있다.
확장 가능한 객체지향 설계가 가지는 특징은 코드의 의존성과 실행 시점의 의존성이 다르다는 것이다.
설계가 유연해질수록 코드를 이해하고 디버깅하기는 점점 더 어려워진다. 반면, 유연성을 억제하면 코드를 이해하고 디버깅하기는 쉬워지지만 재사용성과 확장 가능성은 낮아진다는 사실도 기억. 항상 유연성과 가독성 사이에서 고민해야 한다.
상속 - 클래스의 코드를 전혀 수정하지 않고도 재사용하는 방식 상속은 객체지향에서 코드를 재사용하기 위해 가장 널리 사용되는 방법. 부모 클래스와 다른 부분만을 추가해서 새로운 클래스를 쉽고 빠르게 만드는 방법을 차이에 의한 프로그래밍이라고 부른다.
추상화를 사용하면, 추상화의 계층만 따로 뗴어 놓고 살펴보면 요구사항의 정책을 높은 수준에서 서술할 수 있다는 장점이 있다. 또한, 추상화를 이용하면 설계가 좀 더 유연해진다.
항상 예외 케이스를 최소화하고 일관성을 유지할 수 있는 방법을 선택하라
NoneDiscountPolicy만을 위해 인터페이스를 추가하는 것이 과하다는 생각이 들 수도 있을 것. 구현과 관련된 모든 것들이 트레이드오프의 대상이 될 수 있다.
객체지향 설계와 관련된 자료를 조금이라도 본 사람들은 코드 재사용을 위해서는 상속보다는 합성이 더 좋은 방법이라는 이야기를 많이 들었을 것이다.
💡상속이 캡슐화를 위반하고, 설계를 유연하지 못하게 만든다. 상속을 이용하기 위해서는 부모 클래스의 내부 구조를 잘 알고 있어야한다. → 캡슐화가 약화된다.
상속보다 인스턴스 변수로 관계를 연결한 원래의 설계가 더 유연하다. 합성(composition) 은 결국 DIP(의존성 역전 원칙) 을 의미하는구나.
코드 재사용을 위해서는 상속보다는 합성을 선호하는 것이 더 좋은 방법이다.