아이템18. 상속보다는 컴포지션을 사용하라
2021-10-14 00:00:00 # Effective_Java
  • 그냥 상속은 문제가 많다…
  • 메서드 호출과 달리 상속은 캡슐화를 깨뜨린다.
  • 상위 클래스의 변경으로 하위 클래스에 손을 대지 않아도 오작동할 가능성 있다.
  • 그 외에도 상위 클래스를 변경하다가 하위 클래스의 요소와 충돌할 가능성도 있음

컴포지션 - 상속을 피해가자

  • 새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조하게 하자.
  • 기존 클래스가 새로운 클래스의 구성요소로 쓰인다는 의미에서 컴포지션이라 한다.
  • 전달(forwarding): 새로운 클래스의 인스턴스 메서드들은 기존 클래스에 대응하는 메서드를 호출해 그 결과를 반환한다.
    • 새로운 클래스의 메서드를 전달 메서드라 한다.
  • 컴포지션을 사용할 대부분의 상황에서 상속을 사용하면 내부 구현을 불필요하게 노출하는 꼴이다.
    • 클라이언트가 노출된 내부에 직접 접근할 수도 있다. (서버 클라이언트의 그 클라이언트 아님 주의)
    • 써야할 자식 클래스의 메서드를 사용하는게 아니라 실수로 비슷한 이름의 상위 클래스의 메서드를 사용해버리면 안되니깐~

wrapper class (Decorator pattern)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.util.Collection;

public class InstrumentedSet<E> extends ForwardingSet<E> {
private int addCount = 0;

public InstrumentedSet(Set<E> s) {
super(s);
}

@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}

@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}

public int getAddCount() {
return addCount;
}
}

1
2
3
4
5
6
7
8
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set<E> s) { // Set 인스턴스만 사용
this.s = s;
}

// methods...
}
  • Set의 인스턴스를 인수로 받는 생성자를 제공한다.
  • 임의의 Set에 계측(instrumental) 기능을 덧씌워서 새로운 Set으로 만드는 것이 핵심!
  • 콜백 프레임워크에서는 맞지 않다.

핵심 정의

  • 상속은 강력하지만 캡슐화를 해친다.
  • 그냥 예외를 두지 말고 상속 대신 컴포지션을 사용해라
  • 래퍼 클래스는 하위 클래스보다 견고하고 강력하다.