메서드 호출과 달리 상속은 캡슐화를 깨뜨린다.
public class InstrumentHashSet<E> extends HashSet<E>{
private int addCount = 0;
public InstrumentHashSet(){
}
public InstrumentHashSet(int initCap, float loadFactor){
super(initCap, loadFactor);
}
@Override
public boolean add(E e){
addCount++;
return super.add(e);
}
@Overrid
public boolean addAll(Collection<? extends E> c){
addCount += c.size();
return super.addAll(c);
}
public int getAddCount(){
return addCount;
}
}
public SampleRunner{
public static void main(String[] args) {
InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
s.addAll(List.of("tick", "pung", "ticktick"));
System.out.println(s.getAddCount());
}
}
본래 의도했던 결과값은 3
이지만, 상위 클래스에서도 중복으로 계산되어서 6
으로 출력이 나온다.
기존 클래스를 확장하는 대신, 새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조하자.
전달: 새 클래스의 인스턴스 메서드들은 기존 클래스의 대응하는 메서드를 호출 해 그 결과를 반환하는 방식
전달 메서드 : 전달하는 새 클래스의 메서드
이 전달 메서드들은 기존 클래스의 내부 구현 방식의 영향에서 벗어나며, 새로운 메서드가 추가되더라도 전혀 영향을 받지 않는다.
예제코드
class InstrumentedSet<E> extends ForwardingSet<E>{
private int addCount = 0;
public InstrumentedSet(Set<E> s){
super(s);
}
}
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 int size(){
return s.size();
}
@Override
public boolean isEmpty() {
return false;
}
public Iterator<E> iterator(){
return s.iterator();
}
public boolean add(E e){
return s.add(e);
}
public boolean remove(Object o){
return s.remove(o);
}
public boolean containsAll(Collection<?> c){
return s.containsAll(c);
}
public boolean addAll(Collection<? extends E> c){
return s.addAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
return false;
}
public boolean removeAll(Collection<?> c){
return s.removeAll(c);
}
public Object[] toArray(){
return s.toArray();
}
public <T> T[] toArray(T[] a){
return s.toArray(a);
}
@Override
public String toString() {
return super.toString();
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public Spliterator<E> spliterator() {
return Set.super.spliterator();
}
}
InstrumentedSet은 Set 인터페이스를 활용해 설계되어 Set의 기능에 새로운 계측 기능을 덧씌워 새로운 Set으로 만들어진 클래스이다.
다른 Set인스턴스를 감싸고 있다는 뜻에서 InstrumentedSet은 래퍼 클래스
, 데코레이터 패턴
이라고 한다.
확장하려는 클래스의 API에 아무런 결함이 없는가?
이 결함의 클래스의 API에 전파돼도 괜찮은가?