포스트

11장. 합성과 유연한 설계

오브젝트 읽기12 / 13편 · 목차
  1. 들어가며...
  2. 1장. 객체, 설계
  3. 2장. 객체지향 프로그래밍
  4. 3장. 역할, 책입, 협력
  5. 4장. 설계 품질과 트레이드오프
  6. 5장. 책임 할당하기
  7. 6장. 메시지와 인터페이스
  8. 7장. 객체 분해
  9. 8장. 의존성 관리하기
  10. 9장. 유연한 설계
  11. 10장. 상속과 코드 재사용
  12. 11장. 합성과 유연한 설계
  13. 12장. 다형성
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 라이센스를 따릅니다.