11장: 합성과 유연한 설계
상속과 합성
상속 관계는 is-a 관계라고 부르고 합성 관계는 has-a 관계로 나타낼 수 있다.
상속은 부모 클래스 안에 구현된 코드 자체를 재사용하지만 합성은 포함되는 객체의 퍼블릭 인터페이스를 재사용한다.
상속은 부모 클래스의 내부 구현에 대해 상세하게 알아야 하기 때문에 결합도가 높아진다는 단점이 있다.
합성은 부모 객체의 내부 구현이 아니라 퍼블릭 인터페이스에 의존하므로 결합도가 낮다.
코드 재사용을 위해서는 객체 합성이 클래스 상속보다 더 좋은 방법이다.
상속을 합성으로 바꾸면서 로직의 변경이 필요 없는 부모 클래스의 퍼블릭 인터페이스를 모두 제공하기 위해서 포워딩 메서드를 사용할 수 있다.
인터페이스를 구현할 때 오버라이드 하는 메서드 내에서 내부 인스턴스의 메서드를 그대로 호출할 때 이를 포워딩 메서드라고 부른다.
기존 클래스의 인터페이스를 그대로 외부에 제공하면서 구현에 대한 결합 없이 일부 작동 방식을 변경하고 싶은 경우에 사용할 수 있는 유용한 기 법이다.
상속을 합성으로 바꾸더라도 자식 클래스의 요구사항에 맞게 부모 클래스도 변경해야 하는 파급 효과가 있을 수 있다. 이 때 몽키 패치라는 방식을 사용할 수도 있다.
현재 실행 중인 환경에만 영향을 미치도록 지역적으로 코드를 수정하거나 확장하는 것
Playlist의 코드를 수정할 권한이 없거나 소스코드가 존재하지 않는다고 하더라도 몽키 패치가 지원되는 환경이라면 Playlist에 직접 remove 메서드를 추가하는 것이 가능하다.
자바는 언어 차원에서 몽키 패치를 지원하지 않기 때문에 바이트코드를 직접 변환하거나 AOP(Aspect-Oriented Programming)를 이용해야 한다.
훅 메서드
개방 폐쇄 원칙을 만족시키기 위해 부모 추상 클래스에 새로운 추상 메서드를 추가할 경우 모든 자식 클래스가 이를 구현해야 한다. 만약 여러 자식 클래스에서 구현이 중복될 경우, 편의를 위해 기본 구현이 되어있는 훅 메서드를 제공할 수 있다.
클래스 폭발 문제 해결하기
상속을 이용해 핸드폰 이용 요금에 여러 할인/세금 정책을 붙이려면 아래와 같이 정책마다 자식 클래스를 구현해야 한다. 여러 세금 정책 간의 조합이 필요하다면 해당 조합을 위해 또 다른 자식 클래스를 만들어야 할 것이다.
상속의 남용으로 하나의 기능을 추가하기 위해 필요 이상으로 많은 수의 클래스를 추가 해야 하는 경우 클래스 폭발 문제 혹은 조합의 폭발이라고 부른다.
이러한 문제의 원인은 컴파일 타임에 결정된 자식 클래스와 부모 클래스의 결합이다.
상속을 합성으로 변경하면 컴파일 타임 의존성을 런타임 의존성으로 변경하여 클래스 폭발 문제를 해결할 수 있다.
일단은 Phone을 상속받아 구현하는 RegularPhone/NightlyDiscountPhone을 합성으로 변경한다. 즉, Phone 클래스 내부에서 RatePolicy라는 인터페이스 필드를 가지게 된다.
그리고 부가적으로 적용되는 할인/세금 정책을 적용하기 위해 AdditionalRatePolicy라는 추상 클래스를 만들고 내부에서 함께 적용할 다른 정책을 참조하도록 한다.
부가 비율 할인 정책은 아래와 같이 추상 클래스를 구현하여 만들 수 있다.
이렇게 할 경우 아래와 같이 원하는 정책들을 조합하여 사용할 수 있게 된다.
합성을 이용하면 아래와 같이 조합과 변경에 용이한 다이어그램이 나오게 된다.
믹스인
객체를 생성할 때 코드 일부를 클래스 안에 섞어 넣어 재사용하는 기법
합성이 실행 시점에 객체를 조합하는 재사용 방법이라면 믹스인은 컴파일 시점에 필요한 코드 조각을 조합하는 재사용 방법이다.
자식클래스를 부모 클래스와 동일한 개념적인 범주로 묶어 is-a 관계를 만드는 것이 목적이다.
스칼라에서는 다른 코드와 조합해서 확장할 수 있는 기능을 트레이트로 구현할 수 있다.
아래는 BasicRatePolicy나 BasicRatePolicy을 상속받은 경우에만 믹스인될 수 있는 TaxablePolicy이다.
아래는 기본 요금 정책에 부가 세금 정책을 믹스인 한 클래스이다. 스칼라는 믹스인한 클래스와 트레이트를 선형화하여, 가장 마지막에 정의한 트레이트부터 점점 super를 호출하여 클래스 자신과 부모 클래스 메서드가 호출되게 만든다.
상속은 부모 클래스와 자식 클래스의 관계를 코드를 작성하는 시점에 고정시켜 버리지만(정적) 믹스인은 제약을 둘 뿐 실제로 어떤 코드에 믹스인될 것인지를 결정하지 않는다(동적).
클래스 폭발 문제의 단점은 클래스가 늘어난다는 것이 아니라 클래스가 늘어날수록 중복 코드도 함께 기하급수적으로 늘어난다는 점이다. 믹스인에는 이런 문제가 발생하지 않는다.
믹스인은 항상 상속 계층의 하위에 위치하게 되어 추상 서브클래스라고도 불린다.
믹스인을 사용하면 특정 클래스에 대한 변경/확장을 독립적으로 구현한 후 필요한 시점에 차례로 쌓을 수 있는 변경(stackable modification)이라는 특징을 가진다.
이펙티브 자바에서는 믹스인을 제공하는 대표적인 예로 Comparable을 든다. Comparable 인터페이스를 각 클래스에서 구현해야 한다는 점은 스칼라의 trait와 다르지만, 여러 클래스에서 공통된 기능을 제공한다는 측면에서 보면 믹스인의 일종이라고 느껴지기도 한다.
Last updated