[Java] Item 18 : 상속보다는 컴포지션을 사용하라
Effective Java를 읽고 정리한 정리본입니다.
📌 Item 18 : 상속보다는 컴포지션을 사용하라
🫧 상속의 단점
- 캡슐화를 깨트림
- 상위 클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작에 이상이 생길 수 있음.
✨ 코드 예시
- 처음 생성된 이후 원소가 몇 개 더해졌는지 알 수 있는 변형 HashSet
public class InstrumentedHashSet<E> extends HashSet<E> {
private int addCount = 0; // 추가된 원소의 수
public InstrumentedHashSet(int initCap, float loadFactor) {
super(initCap, loadFactor);
}
@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;
}
}
만약 이 코드에 addAll로 원소 3개를 더했다면? getAddCount에서는 3이 아닌 6을 반환하게 된다. HashSet의 addAll 메서드가 add 메서드를 사용해 구현해서 한 원소에 대해 두 번 카운트되었기 때문이다.
그렇다면 addAll 메서드를 다른 방식으로 재정의하면 어떨까? 주어진 컬렉션을 순회하며 원소 하나당 add 메서드를 한 번만 호출하는 것이다.
만약 이렇게 문제를 회피하려 한다면, 상위 클래스의 메서드 동작을 다시 구현하는 것이 어려울 뿐더러, 하위 클래스에서는 접근할 수 없는 private 필드를 써야 하는 상황이라면 구현 자체가 불가능해질 것이다.
🫧 컴포지션
- 상속의 단점을 해결하기 위해 사용 기존 클래스를 확장하는 대신, 새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조하게 함.
전달
: 새 클래스의 인스턴스 메서드들은 private 필드로 참조하는 기존 클래스의 다응하는 메서드를 호출해 그 결과를 반환하는 것. 여기서 새 클래스의 메서드들은 전달 메서드
라고 부른다.
🫧 래퍼 클래스 - 상속 대신 컴포지션 사용
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;
}
}
🫧 전달 클래스 코드 (재사용 가능)
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set<E> s) { this.s = s };
public void clear() { s.clear(); }
public boolean contains(Object o) { return s. contains(o); }
public boolean isEmpty() { return s.isEmpty(); }
...
}
InstrumentedSet은 HashSet의 모든 기능을 정의한 Set 인터페이스를 활용했다.