11 합성과 유연한 설계
10장은 상속에 대해 이야기를 하였다면 11장에서는 합성에 대해 이야기를 하였습니다. 그리고 10장에서 이야기한 상속과 마찬가지로 합성을 잘 쓰고 있는 저에게 있어 생각하지 못하고 있던 장단점들에 대해 이야기를 하고 있었습니다. 그러면서 부가적으로 일급 컬렉션에 대해 떠올릴 수 있었습니다.
합성은 전체를 표현하는 객체가 부분을 표현하는 객체를 포함해서 부분 객체의 코드를 재사용한다.
...
합성에서 두 객체 사이의 의존성은 런타임에 해결된다.
...
합성은 내부에 포함되는 객체의 구현이 아닌 퍼블릭 인터페이스에 의존한다. 따라서 합성을 이용하면 포함된 객체의 내부 구현이 변경되더라도 영향을 최소화할 수 있기 때문에 변경에 더 안정적인 코드를 얻을 수 있게 된다.
...
[코드 재사용을 위해서는] 객체 합성이 클래스 상속보다 더 좋은 방법이다.
여기까지 읽었을때는 상속은 확장의 경우가 제한될때 즉 상속받은 클래스들의 수가 많지 않을 때 적합하다는 생각을 하였고 합성은 DTO와 같이 클래스의 변경이 빈번하게 일어날 경우 선택하는 것이 좋다 생각하였습니다.
01 상속을 합성으로 변경하기
상속을 남용했을 때 직면할 수 있는 세 가지 문제점을 살펴 봤다.
- 불필요한 인터페이스 상속 문제
- 메서드 오버라이딩의 오작용 문제
- 부모 클래스와 자식 클래스의 동시 수정 문제
11장에서는 10장에서 사용한 상속의 문제점을 해결하기 위한 방법으로 합성을 이용하고 있고 해결하는 방법에 대해 언급하기 이전에 리마인드 하는 차원에서 다시 이야기를 하고 있다 생각합니다.
메서드 오버라이딩의 오작용 문제
InstrumentedHashSet의 코드를 보면 Set의 오퍼레이션을 오버라이딩한 인스턴스 메서드에서 내부의 HashSet 인스턴스에게 동일한 메서드 호출을 그대로 전달하는 것을 알 수 있다. 이를 포워딩(forwarding)이라고 부르고 동일한 메서드를 호출하기 위해 추가된 메서드를 포워딩 메서드라고 부른다. 포워딩은 기존 클래스의 인터페이스를 그대로 외부에 제공하면서 구현에 대한 결합없이 일부 작동 방식을 변경하고 싶은 경우에 사용할 수 잇는 유용한 기법이다.
프로젝트를 하며 Controller - Service - Repository레이어를 가진 프로젝트를 구현하였습니다. 당시 메서드를 단순 호출함에도 불구하고 controller <-> service <-> repository 순으로 거쳐야 하는 것에 대해 의문이 생겼었습니다. 그러나 포워딩이라는 것에 대해 책에서 언급해주었기에 저의 의문은 해결될 수 있었습니다.
02 상속으로 인한 조합의 폭발적인 증가
추상 메서드와 동일하게 자식 클래스에서 오버라이딩할 의도로 메서드를 추가했지만 편의를 위해 기본 구현을 제공하는 메서드를 훅 메서드라고 부른다.
오버라이딩을 하게끔 추상 메서드를 추가하였을 때 다음과 같이 훅 메서드를 구현하는 것 역시 참고해야 할 듯 합니다.
중복의 덫에 걸린다
상속의 남용으로 하나의 기능을 추가하기 위해 필요 이상으로 많은 수의 클래스를 추가해야 하는 경우를 가리켜 클래스 폭팔 문제 또는 조합의 폭팔 문제라고 부른다.
이는 상속을 이용해 로직을 구현한 상황에서 비슷한 로직을 가진 클래스를 추가하거나 수정할 때 생기는 문제를 이야기 합니다. 사실 인터페이스를 이용한 상속을 많이 사용하는 저에게 있어 와닿는 내용은 아니였지만 추상 클래스를 이용해 구현을 할 일이 어쩌다 한번은 생길거라 생각합니다. 그렇기에 이 점에 유의해야 할 듯 합니다.
03 합성 관계로 변경하기
여러 기능을 조합해야 하는 설계에 상속을 이용하면 모든 조합 가능한 경우별로 클래스를 추가해야 한다.
...
합성은 조합을 구성하는 요소들을 개별 클래스로 구현한 후 실행시점에 인스턴스를 조립하는 방법을 사용하는 것이라고 할 수 있다. 컴파일 의존성에 속박되지 않고 다양한 방식의 런타임 의존성을 구성할 수 있다는 것이 합성이 제공하는 가장 커다란 장점인 것이다.
책을 읽으며 해당 부분은 마치 Builder와 유사한 점이 있다 생각합니다. 또한 합성관계로 인스턴스를 생성하기 위해 다형성은 필요하다 생각합니다.
기본 정책 합성하기
다양한 종류의 객체와 협력하기 위해 합성 관계를 사용하는 경우에는 합성하는 객체의 타입을 인터페이스나 추상 클래스로 선언하고 의존성 주입을 사용해 런타임에 필요한 객체를 설정할 수 있도록 구현하는 것이 일반적이다.
알게 모르게 합성을 많이 사용하고 있던 저에게 있어 무의식에 속해있던 지식을 의식으로 퍼올려주었는 부분이였다 생각합니다.
객체 합성이 클래스 상속보다 더 좋은 방법이다.
코드를 재사용하면서도 건전한 결합도를 유지할 수 있는 더 좋은 방법은 합성을 이용하는 것이다. 상속이 구현을 재사용하는 데 비해 합성은 객체의 인터페이스를 재사용한다.
확실히 합성을 이용해 인스턴스를 생성하고 이를 통해 로직을 구현할 경우 클래스의 수가 줄어들고 이해하기 쉽다 생각합니다.
04 믹스인
상속과 클래스를 기반으로 하는 재사용 방법을 사용하면 클래스의 확장과 수정을 일관성 있게 표현할 수 있는 추상화의 부족으로 인해 변경하기 어려운 코드를 얻게 된다.
이는 위에서 나왔던 이야기이지만 여전히 중요하다 생각하기에 다시 한번 더 작성하였습니다.
트레이트로 부가 정책 구현하기
따라서 BasicRatePolicy의 calculateFee 메서드를 오버라이딩한 후 super 호출을 통해 먼저 BasicRatePolicy의 calculate 메서드를 실행한 후 자신의 처리를 수행한다.
...
이제 super 호출을 살펴보자. 방금 전에 트레이트가 부모 클래스를 고정시키지 않는다고 했다. 따라서 super로 참조되는 코드 역시 고정되지 않는다.
이 부분은 믹스인을 구현하는 방법에 대해 이야기를 하는 부분입니다. 그러나 믹스인은 저에게 있어 생소한 부분이였습니다. 또한 책에서 역시 믹스인은 스칼라로 설명이 되어있습니다. 그렇기에 실제 코드가 어떠한 방식으로 동작하는지는 저에게 있어 큰 의미는 없었습니다. 대신 말로 풀어 쓴 동작방식이 저의 흥미를 끌었고 중요하다 생각하였습니다.믹스인의 동작 방식은 먼저 부모 클래스에 존재하는 메서드를 호출하고 반환하는 값을 기반으로 특정 로직을 수행합니다. 그리고 이를 기존의 코드(부모 클래스의 코드)를 자식 클래스에 섞는다 하여 믹스인이라 부릅니다.