상속의 장점
- 코드의 재사용을 통해서 중복을 줄인다.
- 확장성이 증가한다.
- 클래스 간의 계층적 관계를 구성함으로써 다형성을 구현할 수 있다.
- 개발 시간이 단축된다.
상속의 문제점
-
캡슐화를 깨뜨린다
상위 클래스가 구현이 하위 클래스에게 노출되기 때문에 캡슐화가 깨진다. 따라서 자식 클래스가 부모 클래스에 강하게 결합 및 의존하게 된다. 이로 인해 상위 클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작이 달라질 수 있다. 상위 클래스의 내부 구현이 달라지면 코드 한 줄 건드리지 않은 하위 클래스가 오작동할 수 있다. 예시:
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()); }
위 코드를 실행하면 대부분
1
과4
가 나올 것이라는 추측을 한다. 하지만 실제로는1
과7
이 출력된다. 이유는AbstractCollection
의addAll()
메소드를 보면 이해할 수 있다.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
에서addAll
이add
메서드를 이용하여 구현했음을 가정한 해법이다. 부모 클래스의 구현에 의존적인 방법은 좋지않다.addAll()
을 다른 방식으로 재정의한다. 하지만 이 방법은 상위 클래스의 메서드 동작을 다시 구현하기 어렵고, 시간도 더 들고, 오류를 내거나 성능을 떨어뜨릴 수도 있다. 또한 하위 클레스에서는 접근할 수 없는 private 필드를 써야 하는 상황이라면 이 방식으로는 구현 자체가 불가능하다.
-
부모 클래스의 결함도 자식에게 넘어온다. 예시:
Hashtable
과Vector
를 컬렉션 프레임워크에 포함시키자 이를 상속한 크래스들의 보안 구멍들을 수정해야 되는 사태가 벌어졌다.
컴포지션
조합의 장점
- 새로운 클래스는 기존 클래스의 내부 구현 방식의 영향에서 벗아니며, 심지어 기존 클래스에 새로운 메소드를 추가되더라도 전혀 영향을 받지 않는다.
- 메소드를 호출하는 방식으로 동작하기 때문에 캡슐화를 꺠뜨리지 않는다.
- 상위 클래스에 의존하지 않기 때문에 변화에 유연하다.
상속과 차이
일반적으로 상속은 Is-a 관계에 있고, 조합은 Has-a 관계에 있다. 조합은 상속의 문제점인 캡슐화를 보장하고 있기때문에 가능하면 조합을 사용하는 것이 좋다. 상속을 사용하려면 확장이 아니라 정제의 목적일 때 사용하는 것이 좋다. 확장이란 새로운 행도울 덧붙여 개존의행동을 부분적으로 보완하는 것을 의미하고 정제란 부분적으로 불완전한 행동을 완전하게 만드는 것을 의미한다.
참고자료
- Effective Java 3rd Edition: Item 18 상속보다는 컴포지션을 사용하라
댓글남기기