상속의 장점

  • 코드의 재사용을 통해서 중복을 줄인다.
  • 확장성이 증가한다.
  • 클래스 간의 계층적 관계를 구성함으로써 다형성을 구현할 수 있다.
  • 개발 시간이 단축된다.

상속의 문제점

  • 캡슐화를 깨뜨린다

    상위 클래스가 구현이 하위 클래스에게 노출되기 때문에 캡슐화가 깨진다. 따라서 자식 클래스가 부모 클래스에 강하게 결합 및 의존하게 된다. 이로 인해 상위 클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작이 달라질 수 있다. 상위 클래스의 내부 구현이 달라지면 코드 한 줄 건드리지 않은 하위 클래스가 오작동할 수 있다. 예시: HashSet의 상속

      public class MySet<E> extends HashSet<E> {
              private int addCount = 0;
    
              public MySet() {
                  super();
              }
    
              @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 static void main(public static void main(String[] args) {
          MySet<String> mySet = new MySet<>();
          mySet.add("a");
          System.out.println(mySet.getAddCount());
      		mySet.addAll(List.of("a", "b", "c"));
          System.out.println(mySet.getAddCount());
      }
    

    위 코드를 실행하면 대부분 14 가 나올 것이라는 추측을 한다. 하지만 실제로는 17 이 출력된다. 이유는 AbstractCollectionaddAll() 메소드를 보면 이해할 수 있다.

      public boolean addAll(Collection<? extends E> c) {
          boolean modified = false;
          for (E e : c)
              if (add(e))
                   modified = true;
          return modified;
      }
    

    내부에서 add() 를 다시 호출하는데 MySet 은 오버라이딩이 이루어진 add() 를 호출하면서 7 이라는 결과가 나오는 것이다. 이렇게 부모 클래스의 구현에 따라 자식 클래스의 동작이 바뀐다. 즉, 캡슐화가 깨진다. 이 문제를 해결하기 위해서는 3가지 방법을 시도해 볼 수 있다.

    • addAll() 을 재정의하지 않는다. 하지만 이 방법은 HashSet에서 addAlladd 메서드를 이용하여 구현했음을 가정한 해법이다. 부모 클래스의 구현에 의존적인 방법은 좋지않다.
    • addAll()을 다른 방식으로 재정의한다. 하지만 이 방법은 상위 클래스의 메서드 동작을 다시 구현하기 어렵고, 시간도 더 들고, 오류를 내거나 성능을 떨어뜨릴 수도 있다. 또한 하위 클레스에서는 접근할 수 없는 private 필드를 써야 하는 상황이라면 이 방식으로는 구현 자체가 불가능하다.
  • 부모 클래스의 결함도 자식에게 넘어온다. 예시: HashtableVector를 컬렉션 프레임워크에 포함시키자 이를 상속한 크래스들의 보안 구멍들을 수정해야 되는 사태가 벌어졌다.

컴포지션

조합의 장점

  • 새로운 클래스는 기존 클래스의 내부 구현 방식의 영향에서 벗아니며, 심지어 기존 클래스에 새로운 메소드를 추가되더라도 전혀 영향을 받지 않는다.
  • 메소드를 호출하는 방식으로 동작하기 때문에 캡슐화를 꺠뜨리지 않는다.
  • 상위 클래스에 의존하지 않기 때문에 변화에 유연하다.

상속과 차이

일반적으로 상속은 Is-a 관계에 있고, 조합은 Has-a 관계에 있다. 조합은 상속의 문제점인 캡슐화를 보장하고 있기때문에 가능하면 조합을 사용하는 것이 좋다. 상속을 사용하려면 확장이 아니라 정제의 목적일 때 사용하는 것이 좋다. 확장이란 새로운 행도울 덧붙여 개존의행동을 부분적으로 보완하는 것을 의미하고 정제란 부분적으로 불완전한 행동을 완전하게 만드는 것을 의미한다.

참고자료

  • Effective Java 3rd Edition: Item 18 상속보다는 컴포지션을 사용하라

댓글남기기