컴포지션?
private 필드를 통해 기존 클래스가 새로운 클래스의 구성요소(인스턴스)로 쓰이는 것
새로운 클래스에 기존 클래스의 영향이 적어 기존 클래스가 변경되어도 안전
public class NoteBook{
private final Keyboard keyboard;
...
public Notebook(Keyboard keyboard){
this.keyboard = keyboard;
}
}
public class Keyboard{
...
}
public class InstrumentedHashSet<E> extends HashSet<E> {
private int addCount = 0;
public InstrumentedHashSet() {
}
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;
}
}
public class main {
public static void main(String[] args) {
InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
s.addAll(List.of("tic", "tac", "toe"));
System.out.println(s.getAddCount());
}
}
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
HashSet
의 addAll은 InstrumentedHashSet
에서 재정의된 add를 호출하기 때문에 addAll에서 +3, add에서 +3, 총 6이 된다기존 클래스를 확장하는 대신 새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조하게 한다.
public class ForwardingSet<E> implements Set<E> {
private final Set<E> set;
public ForwardingSet(Set<E> set) { this.set = set; }
public void clear() { set.clear(); }
public boolean isEmpty() { return set.isEmpbty(); }
public int size() { return s.size(); }
public boolean add(E e) { return set.add(e); }
public boolean addAll(Collection<? extends E> c) { return set.addAll(c); }
...
}
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> collection) {
addCount = addCount + collection.size();
return super.addAll(collection);
}
public int getAddCount() {
return addCount;
}
}
래퍼 클래스는 단점이 거의 없다. 단 콜백 프레임워크와는 어울리지 않는다는 점을 주의하자. 콜백 프레임워크는 자기 자신의 참조를 다른 객체에 넘겨서 다음 콜백 때 사용하도록 한다. 내부 객체는 자신을 래핑하고 있는 래퍼의 존재를 모르니 this의 참조를 넘기고 콜백 때는 래퍼가 아닌 내부 객체를 호출하게 된다.
컴포지션은 이러한 결함을 숨기는 새로운 API를 설계할 수 있지만, 상속은 상위클래스의 API 결함까지도 그대로 상속한다.