public class BluewSet<E> extends HashSet<E> {
private int addCount = 0; // 추가된 원소의 개수
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount = addCount + c.size(0;
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
// 객체 생성 후 3개의 엘리먼트를 addAll 메서드로 추가
BluewSet bluewSet = new BluewSet<>();
bluewSet.addAll(List.of("사과", "딸기", "바나나"));
- addAll을 이용해서 3개의 원소를 추가했다고 가정하자.
- getAddCount의 결과는?? 3을 기대하지만 결과는 6!
- HashSet의 addAll은 add를 호출함 -> BluewSet은 add를 overriding을 했기 때문에 HashSet이 아닌 BluewSet.add가 호출
- BluewSet의 add에서 addCount를 하나씩 증가
- 문제 해결은?
- addAll을 재정의 하지 않으면 되지 않나.. 당장 해결은 되겠지만 HashSet의 addAll이 결국 add를 활용하기 때문에 한계가 있다.
- addAll을 다른 식으로 재정의? 전달받은 List를 순회하면서 일일이 add 호출
- HashSet.addAll 구현 내용
```java
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
첫째 도찐개찐이고, 둘째 괜히 내가 잘못 구현했다가는 성능 떨어지고, 셋째 알지 못하는 버그를 생산할 가능성이 넘나 높다.
하위 클래스가 깨지기 쉬운 케이스가 발생한다.
다음 릴리즈에서 상위 클래스에 새로운 메서드를 추가한다면?
하위 클래스에서 제대로 재정의를 해주지 못한 상황에서 새로운 메서드를 이용해서 뭔가 조작하면..
허용되지 않은 원소 추가나 혹은 예상치 못한 부작용이 발생할 가능성이 있다.
실제 Collection Framework에 Hashtable, Vector를 포함시키 유사한 이슈가 생겨 수정해야하는 상황이 있었다.
기존 클래스의 인스턴스를 신규 클래스의 구성요소로 쓴다는 점에서 Composition이라 한다.
신규 클래스의 메소드는 기존 클래서의 대응하는 메소드를 호출로 대신하면 전달, forwarding이라 한다.
그래서 좀 더 나은 Sample
public class NewBluewSet<E> extends ForwardingSet<E> {
private int addCount = 0;
public NewBluewSet(Set<E> set) {
super(set);
}
@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;
}
}
public class ForwardingSet implements Set {
private final Set set;
public ForwardingSet(Set set) { this.set = set; }
public void clear() { set.clear(); }
public boolean isEmpty() { return set.isEmpty(); }
public boolean add(E e) { return set.add(e); }
public boolean addAll(Collection<? extends E> c) { return set.addAll(c); }
// ... 블라블라..
}
- NewBluewSet은 Set interface를 활용해서 설계 + Set 인스턴스를 인수로 받는 생성자 제공
- Set의 여러 구현제를 계속 활용 가능
- HashSet, TreeSet 등등등
- Wrapper Class, [Decorator pattern](https://gmlwjd9405.github.io/2018/07/09/decorator-pattern.html), [Delegation](https://june0122.github.io/2021/08/21/design-pattern-delegate/)
### 아니아니 그럼 상속은 언제 왜~
- 반드시 하위 클래스가 상위 클래서의 "진짜" 하위 타입인 상황에서만 쓰자
- [is-a 관계](https://codedragon.tistory.com/5358)
- 비슷한 속성 및 동작을 가지는 객체
- [is-a 관계와 has-a 관계 차이](https://unit-15.tistory.com/entry/Java-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-IS-A-%EA%B4%80%EA%B3%84-HAS-A-%EA%B4%80%EA%B3%84-%EC%B0%A8%EC%9D%B4-%EC%83%81%EC%86%8D-Inheritance)
### 책에서 말하는 핵심 정리
> 상속은 강력하지만 캡슐화를 해친다
> 클래스의 관계가 is-a인 경우면 사용해야한다. 단 이런 관계라도 안심할 수는 없다.
> 상속 대신 Composition, 전달 등의 패턴등을 활용하자. 아니면 Wrapper Class로~
상속
메소드 호출과 달리 상속은 캡슐화를 깨뜨린다.
Sample
// 객체 생성 후 3개의 엘리먼트를 addAll 메서드로 추가 BluewSet bluewSet = new BluewSet<>();
bluewSet.addAll(List.of("사과", "딸기", "바나나"));
// 출력되는 값은? System.out.println(bluewSet.getAddCount());
그래서 상속 대신 Composition을~
그래서 좀 더 나은 Sample
public class ForwardingSet implements Set {
private final Set set;
public ForwardingSet(Set set) { this.set = set; }
public void clear() { set.clear(); }
public boolean isEmpty() { return set.isEmpty(); }
public boolean add(E e) { return set.add(e); }
public boolean addAll(Collection<? extends E> c) { return set.addAll(c); }
// ... 블라블라..
}