본문 바로가기
Java

Effective Java 아이템 18. 상속보다는 컴포지션을 사용하라

by jayden-lee 2019. 4. 14.
728x90
Effective Java 3판을 학습하며 요약한 내용입니다. 자세한 내용은 책을 참고해주시기 바랍니다.

상속은 코드를 재사용하는 강력한 수단이지만 항상 최선의 방법은 아니다. 잘못 사용하게 되면 오류를 범하기 쉽다. 여기에서 말하는 상속은 "클래스가 다른 클래스를 확장(extends)하는" 구현 상속을 말한다. 클래스가 인터페이스를 구현하거나 또는 인터페이스가 또 다른 인터페이스를 확장하는 상속과는 무관하다.

 

상속을 이용하면 캡슐화 원칙을 위반하게 된다. 하위 클래스는 상위 클래스를 확장 했기 때문에 상위 클래스의 구현에 의존적일 수 밖에 없다. 따라서 상위 클래스 변화에 따라 하위 클래스도 발맞춰 변경 해야 한다. 그리고 다음 릴리스에서 상위 클래스에 새로운 메서드가 추가 되면, 하위 클래스는 추가된 메서드 호출에 따른 로직이 변경된 부분을 파악 해야 한다.

 

이러한 문제를 피하려면 상속하는 대신 컴포지션(composition: 구성) 기법을 사용하면 된다. 새로운 클래스에 기존 클래스 객체를 참조할 수 있도록 private 필드 하나를 두는 것이다. 이런 설계 기법을 구성이라고 하며, 기존 클래스는 새로운 클래스의 일부가 된다. 새로운 클래스 메서드에서 기존 클래스의 메서드들 가운데 필요한 것을 호출해서 결과를 반환하면 된다.

 

이러한 구현 기법을 전달(forwarding)이라고 하며, 전달 기법을 구현한 메서드를 전달 메서드(forwarding method)라고 부른다.

 

다음 코드는 집합 래퍼 클래스와 전달 메서드만으로 구성된 클래스이다.

// 래퍼 클래스
public class InstrumentedSet<E> extends ForwardingSet<E> {

    private int addCount = 0;

    public InstrumentedSet(Set<E> 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;
    }
}
// 재사용 가능한 전달 클래스
public class ForwardingSet<E> implements Set<E> {

    private final Set<E> set;

    public ForwardingSet(Set<E> set) {
          this.set = set;
    }

    @Override
    public int size() {
          return set.size();
    }

    @Override
    public boolean isEmpty() {
          return set.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
          return set.contains(o);
    }

    @Override
    public Iterator<E> iterator() {
          return set.iterator();
    }

      ...
}

InstrumentedSet 클래스는 다른 Set 객체를 포장하고(wrapper) 있기 때문에 포장(래퍼) 클래스라고 부른다. 그리고 다른 Set 클래스를 덮어 씌워서 기능을 확장한다고 해서 데코레이터(decorator) 패턴이라고 한다. 상속 대신 구성을 하게 되면 기존 클래스의 구현 세부사항에 종속되지 않기 때문에 새로 만든 클래스는 영향을 적게 받는다.

 

두 클래스의 관계가 "IS-A" 관계가 성립할 때만 상속을 사용해야 한다. 상위 클래스가 상속을 고려해서 만들어진 것이 아니라면 하위 클래스는 당연히 깨지기 쉬워진다. 그리고 상위 클래스에 어떻게 동작하는지에 따른 문서가 없다면 하위 클래스는 상속하려고 할 때 조심스러워야 한다. 그러므로 이러한 문제를 피하기 위해서는 구성과 전달 기법을 사용하는 것이 좋다.

댓글