@Testvoidtest(){finalInstrumentedHashSet<String>insSet=newInstrumentedHashSet<>();System.out.println("test 매서드에서 add 1개 시작");insSet.add("kim");System.out.println("test 매서드에서 addAll 2개 시작");insSet.addAll(List.of("lee","choi"));System.out.println("insSet = "+insSet);System.out.println("insSet.getAddCount() = "+insSet.getAddCount());}
테스트의 결과는 아래와 같다.
test 매서드에서 add 1개 시작
add called
test 매서드에서 addAll 2개 시작
addAll called
add called
add called
insSet = [choi, lee, kim]
insSet.getAddCount() = 5
의도와 달리 insSet.getAddCount() 의 결과는 5이다. 왜냐하면 addAll은 add를 활용하기 때문이다. addAll로 갯수를 센 다음, add를 통해 중복하여 한 번 더 갯수를 센다.
상속을 통해 메서드를 오버라이딩 하는 것은 이처럼 내부 구현에 대한 이해를 전제하게 된다. 이 경우 상위 클래스를 구현하는 사람은 상속을 전제하여 복잡한 설명을 요구받게 되며, 하위 클래스를 구현하는 사람은 해당 클래스에 대한 복잡한 이해를 전제하게 된다. 컴퍼넌트가 외부에 노출된 API만을 가지고 사용한다는 규칙으로부터 멀리 떨어지게 된다.
그 외 상위 클래스의 내부 로직의 변경, 메서드의 생성 등 다양한 변경 과정에서, 하위 클래스에 안정성을 보장할 수 없다.
그러므로 결과적으로 extends를 통한 상속은 최소한으로 해야한다. 만약 상속을 사용한다면 isA의 원칙을 지켜야 한다.
그 보다는 컴포지션을 사용한다.
컴포지션 예제
인터페이스를 구현하는 FowardingSet을 만든다.
Set 구현 클래스의 인스턴스를 필드값으로 가진다. 새로운 클래스의 구성요소로 쓰인다는 뜻으로 컴포지션Composition 이라 한다.
해당 구현 클래스의 메서드를 자신의 구현 메서드로 한다. 이 방식을 전달Forwarding이라 하며 전달 메서드Forwarding Method라 한다.
노출된 API만을 활용하여, 상위 클래스의 내부 구현에 대한 어떤 고민도 필요하지 않게 된다. 메서드의 오버라이딩에 대한 부담감을 없애고 필요로한 내용을 구현할 수 있다.