포스트

11장. 합성과 유연한 설계

11장. 합성과 유연한 설계

상속과 합성은 재사용 대상이 다르다. 상속은 부모 클래스 안에 구현된 코드 자체를 재사용하지만 합성은 포함되는 객체의 퍼블릭 인터페이스를 재사용한다.


❐ 1. 상속을 합성으로 변경하기


1-1. 불필요한 인터페이스 상속 문제

  • 10장에서 살펴본 내용



❐ 2. 상속으로 인한 조합의 폭발적인 증가


2-1. 상속으로 부터 생기는 문제

클래스 폭발 (Class Explosion) or 조합의 폭발(Combinational Explosion)

  • 상속의 남용으로 하나의 기능을 추가하기 위해 필요 이상으로 많은 수의 클래스를 추가해야 하는 경우
  • 이 문제는 자식 클래스가 부모 클래스의 구현에 강하게 결합되도록 강요하는 상속의 근본적인 한계 때문에 발생하는 문제다.



❐ 3. 합성 관계로 변경하기


3-1. 합성

합성의 특징

  • 구현이 아닌 퍼블릭 인터페이스에 대해서만 의존할 수 있기 때문에 컴파일타임에 객체의 관계를 변경하 수 있다.
  • 조합을 구성하는 요소들을 개별 클래스로 구현한 후 실행 시점에 인스턴스를 조립하는 방법을 사용하는 것.


3-2. 기본 정책 합성하기

RatePolicy & Phone

1
2
3
4
// RatePolicy.java
public interface RatePolicy {
    Money calculateFee(List<Call> calls);
}
  • Phone이 다양한 요금 정책과 협력할 수 있어야 하므로 요금 정책 인터페이스를 정의


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Phone.java
public class Phone {
    private RatePolicy ratePolicy;
    private List<Call> calls = new ArrayList<>();
    
    public Phone(RatePolicy ratePolicy) {
        this.ratePolicy = ratePolicy;
    }
    
    pulbic List<Call> getCalls() {
        return calls;
    }
    
    public Money calculateFee() {
        return ratePolicy.calculateFee(calls);
    }
}
  • Phone 내부에 RatePolicy에 대한 참조가 포함되어 있다. → 합성


합성 관계를 사용한 기본 정책의 전체적인 구조


3-3. 부가 정책 적용하기

부가정책을 추가하기 예시

  • 부가 정책은 기본 정책에 대하나 계산이 끝난 후에 적용된다.
  • 부가 정책은 기본 정책이나 다른 부가 정책의 인스턴스를 참조할 수 있어야 한다.
  • 부가 정책은 기본 정책과 동일한 RatePolicy 인터페이스를 구현해야 한다.


부가 정책 구현하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class AdditionalRatePolicy implements RatePolicy {
    private RatePolicy next;
    
    public AdditionaRatePolicy(RatePolicy next) {
        this.next = next;
    }
    
    @Override
    public Money calculateFee(Phone phone) {
        Money fee = next.calculateFee(phone);
        return afterCalculated(fee);
    }
    
    abstract protected Money afterCalculated(Money fee);
}


새로운 정책 추가하기

  • 요구사항을 변경하기 위해서 오직 하나의 클래스만 추가했다.


3-4. 객체 합성이 클래스 상속보다 더 좋은 방법이다.

합성을 쓰자

  • 상속 좋음. 근데 코드 재사용을 위한 우아한 해결책은 아니다.
  • 합성은 객체의 인터페이스를 재사용한다.


그럼 상속을 쓰지 말라는 것인가?

  • 우선 상속을 구현 상속인터페이스 상속의 두 가지로 나누어 이해해라.
    • 참고로 이번 장에서 살펴본 상속에 대한 모든 단점들은 구현 상속에 국한된다.
    • 인터페이스 상속은 13장에서 자세히…



❐ 4. 믹스인


4-1. 믹스인이란

정의

  • 객체를 생성할 때 코드 일부를 클래스 안에 섞어 넣어 재사용하는 기법.
  • 믹스인은 컴파일 시점에 필요한 코드 조각을 조합하는 재사용 방법.
  • 코드 재사용에 특화된 방법이면서 상속과 같은 결합도 문제를 초래하지 않는다.
  • 이해하기 쉽게 정리하면
    • 특정 기능을 담은 클래스를 다른 클래스에 섞어서(mix in) 사용하기 위한 재사용 단위
    • is-a 관계를 만들고 싶지는 않지만, 기능은 재사용하고 싶을 때 상속대신 사용하는 패턴


핵심 아이디어

  • 기능은 상속이 아니라 조합(Combination) + 인터페이스로 제공하자
  • 자바에서는 보통 아래 2가지로 구현한다.
    1. 인터페이스 + default 메서드
    2. 위임(delegation) 객체 조합


4-2. 예제로 이해하기

Timestamp 기능 믹스인 (기능 조합 목적)

1
2
3
4
5
6
7
8
9
10
public interface Timestamped {

    LocalDateTime getCreatedAt();

    void setCreatedAt(LocalDateTime time);

    default void markCreated() {
        setCreatedAt(LocalDateTime.now());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class User implements Timestamped {

    private LocalDateTime createdAt;

    @Override
    public LocalDateTime getCreatedAt() {
        return createdAt;
    }

    @Override
    public void setCreatedAt(LocalDateTime time) {
        this.createdAt = time;
    }
}
1
2
User user = new User();
user.markCreated(); // 믹스인 기능 사용
  • User
    • Timestamped이다(is-a)라고 말하고 있는게 아니라
    • Timestamped 기능을 가진다(has-a)에 가깝다.


다중 기능 조합 방식

1
2
3
4
5
6
7
8
9
public interface SoftDeletable {

    boolean isDeleted();
    void setDeleted(boolean deleted);

    default void delete() {
        setDeleted(true);
    }
}
1
2
3
public class Article implements Timestamped, SoftDeletable {
    //...
}


4-3. 믹스인은 정리.

정리

  • 모든 믹스인은 인터페이스이지만, 모든 인터페이스가 믹스인은 아니다.
  • 문법적으로 is-a, 의미적으로 has-a인 경우가 믹스인이다.
  • 믹스인은 이분법이 아니라, 스펙트럼이다.
분류 예시 특징
🔴 강한 믹스인 Timestamped, SoftDeletable 횡단 관심사, 부가 기능
🟡 약한 믹스인 (경계) Comparable, ComparableEntity 도메인 규칙 + 기능 조합
🔵 타입 인터페이스 PaymentProcessor 다형성, 정체성


근데 그냥 추상화라고 부르면 안됨..? 굳이 믹스인이라고 해야하나…

  • 믹스인이라는 용어가 필요한 상황은
    • “이 인터페이스는 타입을 만들기 위한 게 아니라 기능을 여러 클래스에 섞기 위한 용도다” 라는 의도를 강조하고 싶을 때
  • 걍 실무에서는 추상화, 인터페이스 이렇게 말하면 될듯
    • 그리고 추상화를 구현하거나, 합성을 통해서 사용하니깐 믹스인이라는걸 생각안할거 같음.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.