💡 상속 : 하위 클래스가 상위 클래스의 특성을 재정의 한 것 (IS-A 관계)
💡 컴포지션 : 기존 클래스가 새로운 클래스의 구성요소가 되는 것 (HAS-A 관계)
상위 클래스가 어떻게 구현되어있느냐에 따라 하위 클래스의 동작에 이상이 생길 수 있다.
import java.util.Collection;
import java.util.HashSet;
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;
}
}
import java.util.List;
public class Main {
public static void main(String[] args) {
InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
s.addAll(List.of("용", "용용", "용용용"));
System.out.println(s.getAddCount());
}
}
HashSet
의 addAll
은 각 원소마다 add
를 호출하게 구현되어있기 때문에 기대와 다르게 아래와 같이 6개 출력된다.
@Override
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c) {
if (add(e)) {
modified = true;
}
}
return modified;
}
재정의를 하면 기대한 값이 나올 수 있다.
즉, 상속의 개념은 우리가 편하게 사용하기 위해 등장했지만, 객체지향 프로그래밍의 캡슐화에 위배될 가능성이 있고, 코드의 유연성도 떨어진다!!
그래서 등장한 컴포지션!
기존 클래스를 확장하는 것 대신 새로운 클래스를 만들고 private
필드로 기존 클래스의 인스턴스를 참조하는 방법
InstrumentedSet
와 같이 Set
인스턴스를 감싸고 기능을 덧씌운다는 뜻HashSet 클래스는 기능을 규정하는 Set 인터페이스가 있으므로 CustomHashSet 클래스를 Forwarding 방식으로 구현할 수 있다.
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
public class ForwardingSet<E> implements Set<E> {
// 기존 클래스를 Private 인스턴스로 선언
private final Set<E> s;
public ForwardingSet(Set<E> s) { this.s = s; }
// Set methods -> 기존 클래스에 대응하는 메서드를 호출
@Override public int size() { return s.size(); }
@Override public boolean isEmpty() { return s.isEmpty(); }
@Override public boolean contains(Object o) { return s.contains(o); }
@Override public Iterator<E> iterator() { return s.iterator(); }
@Override public Object[] toArray() { return s.toArray(); }
@Override public <T> T[] toArray(T[] a) { return s.toArray(a); }
@Override public boolean add(E e) { return s.add(e); }
@Override public boolean remove(Object o) { return s.remove(o); }
@Override public boolean containsAll(Collection<?> c) { return s.containsAll(c); }
@Override public boolean addAll(Collection<? extends E> c) { return s.addAll(c); }
@Override public boolean retainAll(Collection<?> c) { return s.retainAll(c); }
@Override public boolean removeAll(Collection<?> c) { return s.removeAll(c); }
@Override public void clear() { s.clear(); }
}
import java.util.Collection;
import java.util.Set;
public class CustomHashSet<E> extends ForwardingSet<E> {
private int addCount = 0;
public CustomHashSet(Set 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 Main {
public static void main(String[] args) {
CustomHashSet<String> customHashSet = new CustomHashSet<>(new HashSet());
List<String> test = Arrays.asList("ㅎ", "ㅋ", "ㅗ");
customHashSet.addAll(test);
System.out.println(customHashSet.getAddCount());
}
}
Set
인터페이스를 구현하는 전달 클래스를 상속하며 CustomHashSet
클래스는 Set
인터페이스를 구현하며 Set
객체를 인자로 받는 생성자를 갖고 있다.
즉 CustomHashSet
클래스는 어떤 Set
객체를 인자로 받아, 필요한 기능을 가지고있는 Set
객체로 변환시켜주는 역할을 한다. 이러한 클래스를 래퍼(Wrapper) 클래스라고 한다. 다른 Set
객체를 포장하고 있다라는 의미이다.
ForwardingSet
클래스에서는 private final
필드로 Set
객체를 생성하였다.
- 상속은 반드시 하위 클래스가 상위 클래스의 진짜 하위 타입인 상황 에서만 사용해야한다.
- 클래스 간에 IS-A 관계일 경우에만 사용해야한다.