1. 추상 클래스와 인터페이스 트레이드오프
1-1)
1-1)-1. NoneDiscountPolicy
getDiscountAmount() 메서드가 어떤 값을 반환해도 상관 없다는 사실을 알 수 있음.
부모 클래스인 DiscountPolicy에서 할인 조건이 없을 경우에는 getDiscountAmount() 메서드를 호출하지 않음.
1-1)-2. DiscountPolicy
부모 클래스인 DiscountPolicy와 NoneDiscountPolicy를 개념적으로 결합시킴.
NoneDiscountPolicy의 개발자는 getDiscountAmount()가 호출되지 않을 경우
DiscountPolicy가 0을 반환할 것이라는 사실을 가정하고 있기 때문이다.
1-2) 해결방법
1-2)-1. DiscountPolicy → DefaultDiscountPolicy
1-2)-2. DiscountPolicy Interface
1-2)-3. NoneDiscountPolicy
1-3) 인터페이스를 이용해 구현한 DiscountPolicy 계층 Image
어떤 설계가 좋은가 ? 이상적으로는 인터페이스를 사용하도록 변경한 설계가 더 좋을 거임.
현실적으로 NoneDiscountPolicy만을 위해 인터페이스를 추가하는 것이 과하다는 생각이 들 수도
구현과 관련된 모든 것들이 트레이드오프의 대상이 될 수 있다는 사실.
모든 코드에는 합당한 이유가 있어야 한다.
2. 상속과 합성
2-1) 상속의 단점
2-2) 상속의 가장 큰 문제점
캡슐화를 위반한다는 것이다. 상속을 이용하기 위해 부모 클래스와 내부 구조를 잘 알고 있어야함.
부모 클래스의 구현이 자식 클래스에게 노출되기 때문에 캡슐화가 약화된다.
캡슐화의 약화
자식 클래스가 부모 클래스에 강하게 결합되도록 만들기 때문에 부모 클래스를 변경할 때
자식 클래스도 함께 변경될 확률을 높인다.
결과적으로 상속을 과도하게 사용한 코드는 변경하기도 어려워짐.
유연한 설계의 문제점
상속은 부모 클래스와 자식 클래스 사이의 관계를 컴파일 시점에 결정한다.
따라서 실행 시점에 객체의 종류를 변경하는 것이 불가능하다.
예를 들면 실행 시점에 금액 할인 정책인 영화를 비율 할인 정책으로 변경한다고 가정하면
상속을 사용한 설계에서는 AmountDiscountMovie의 객체를
PercentDiscountMovie의 객체로 변경해야 함.
2-3) 합성(인터페이스로 구현)
Movie는 요금을 계산하기 위해 DicountPolicyt의 코드를 재사용한다.
상속과 다른 점은 상속이 부모 클래스의 코드와 자식 클래스의 코드를 컴파일 시점에 하나의 단위로 강하게 결합하는 데 비해 Movie가 DiscountPolicy의 인터페이스를 통해 약하게 결합되는 것이다.
실제로 Movie는 DiscountPolicy가 외부에 calculateDiscountAmount 메서드를 제공한다는 사실만 알고 내부 구현에 대해 전혀 알지 못한다.
이처럼 인터페이스에 정의된 메시지를 통해서만 코드를 재사용하는 방법을 합성이라고 함.
2-3)-1. 합성의 장점
인터페이스를 정의된 메시지를 통해서만 재사용이 가능하기 때문에 구현을 효과적으로
캡슐화할 수 있다.
의존하는 인스턴스를 교체하는 것이 비교적 쉽기 때문에 구현 설계를 유연하게 만든다.
2-3)-2. 상속 vs 합성
2-3)-3. 상속은 사용하면 안되는 건가?
대부분의 설계에서는 상속과 합성을 함께 사용해야한다.
Movie와 DiscountPolicy는 합성 관계로 연결돼 있다.
DiscountPolicy와 AmountDiscountPolicy,
PercentDiscountPolicy는 상속 관계로 연결
코드의 재사용하는 경우
재사용성 측면에서는 상속보다 합성을 선호하는 것이 옳지만
다형성(메시지를 수신하는 객체의 클래스가 무엇인가에 따라 달라지는 다형성)을 위해
인터페이스를 재사용하는 경우에는 상속과 합성을 함께 조합해서 사용할 수밖에 없다.
다형성
Moive는 동일한 메시지를 전송하지만 실제로 어떤 메서드가 실행될 것인지는
메시지를 수신하는 객체의 클래스가 무엇인가에 따라 달라진다. 이를 다형성이라고 함.