http://aeternum.egloos.com/3013830
http://aeternum.egloos.com/3028192
상속의 가장 큰 단점은 캡슐화를 깨뜨린다는 것이다.
부모 클래스가 어떻게 구현되느냐에 따라서 자식 클래스가 영향을 크게 받기 때문에 하위 클래스가 코드 한 줄 건드리지 않았을 언정 상위 클래스가 변경됨으로써 원하는 결과를 얻는데 실패할 수 있다.
릴리스마다 상위 클래스는 내부 구현이 달라질 수 있기 때문에 설계자는 상속을 충분히 고려한 설계를 해야 하고 이를 문서화해야 한다.
HashSet을 상속하는 클래스를 구현해보자 - add 할 때마다 count 증가
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;
}
}
전혀 문제가 없어 보이는 코드지만 실제로 호출할 때 문제가 발생한다.
class Main {
public static void main(String[] args) {
InstrumentedHashSet instance = new InstrumentedHashSet();
instance.addAll(List.of("choi","jun","woo"));
System.out.println(instance.getAddCount());
}
}
3개의 String 값을 addAll 하였지만 결과는 3이 아닌 6이다.
왜일까?
allAll을 재정의 하였기 때문에 하위 클래스에서 3번 등록하고 상위 클래스에도 3번 등록하기 때문이다. (반복되는 작업)
해결 방법 : Wrapper Class
wrapper 클래스란?
특정 인스턴스를 감싸고 있는 클래스
public class InstrumentedHashSet2<E> implements Set<E> {
private final Set<E> s;
public InstrumentedHashSet2(Set set) {
this.s = set;
}
public void clear() { s.clear(); }
public boolean contains(Object o) { return s.contains(o); }
public boolean isEmpty() { return s.isEmpty(); }
public int size() { return s.size(); }
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); }
public boolean removeAll(Collection<?> c)
{ return s.removeAll(c); }
public boolean retainAll(Collection<?> c)
{ return s.retainAll(c); }
public Object[] toArray() { return s.toArray(); }
public <T> T[] toArray(T[] a) { return s.toArray(a); }
@Override public boolean equals(Object o)
{ return s.equals(o); }
@Override public int hashCode() { return s.hashCode(); }
@Override public String toString() { return s.toString(); }
}
public class InstrumentedHashSet3<E> extends InstrumentedHashSet2<E>{
private int addCount = 0;
public InstrumentedHashSet3(Set set) {
super(set);
}
@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;
}
}
위의 코드는 HashSet을 직접적으로 상속받지 않고 HashSet이 구현하는 Set 인터페이스를 재정의한 Wrapper 클래스를 만든다.
그리고 해당 Wrapper 클래스를 상속받는다.
public InstrumentedHashSet3(Set set) {
super(set);
}
또한 인스턴스 생성 시 파라미터로 Set 타입을 받아서 부모의 인스턴스를 생성한다.
재정의는 자식 클래스에서 하지 않으며 모든 재정의는 부모 클래스 (Wrapper 클래스)에서 한다. 자식은 부모 클래스에서 만든 것을 가져다 쓸 뿐이다.
상속을 굳이 사용하고자 한다면 두 가지를 되새겨 보자
1. is a 관계인가?
2. 확장하는 클래스의 API에는 결함이 정말 없는가?
References
|
code Link
https://github.com/mike6321/PURE_JAVA/tree/master/EffectiveStudy
'Java > Java' 카테고리의 다른 글
(JAVA) Interning of String in JAVA (0) | 2020.04.02 |
---|---|
(JAVA) static keyword in java (0) | 2020.03.04 |
(JAVA) private final 과 private static final (0) | 2020.02.21 |
(JAVA) 클래스와 멤버의 접근 권한을 최소화하라 (0) | 2020.02.20 |
(JAVA) The final keyword in Java (0) | 2020.02.11 |