Ch11 합성과 유연한 설계

본문


들어가며

코드 재사용과 관련하여 상속은 간편한 방법이지만 부모 클래스와 자식 클래스의 결합 때문에 변경에 불안정하고 유연하지 못하다.

이번 장에서는 상속 대신 합성을 사용함으로써 어떻게 유연한 설계를 얻을 수 있는지에 대해서 알아본다.


핵심.

  • 상속이 부모 클래스와 자식 클래스를 연결하여 부모 클래스의 코드를 재사용하는데 반해 합성은 전체를 표현하는 객체가 부분을 표현하는 객체를 포함하여 부분 객체의 코드를 재사용한다.

  • 상속에서 부모 클래스와 자식 클래스 사이의 의존성컴파일 타임에 해결되지만 합성에서 두 객체 사이의 의존성은 런타임에 해결된다.

  • 상속은 부모 클래스의 내부 구현에 대해 상세히 알아야 하기 때문에 자식 클래스와 부모 클래스 사이의 결합도가 높을 수 밖에 없다.

  • 합성은 구현에 의존하지 않는다는 점에서 상속과 다르다. 합성은 내부에 포함되는 객체의 구현이 아닌 퍼블릭 인터페이스에 의존한다.

  • 합성을 이용하면 포함된 객체의 내부 구현이 변경되더라도 영향을 최소화 할 수 있기 때문에 변경에 더 안정적인 코드를 얻을 수 있다.

  • 상속 관계는 클래스 사이의 정적인 관계인 데 비해 합성 관계는 객체 사이의 동적인 관계다.

  • 코드 작성 시점에 결정한 상속 관계는 변경이 불가능하지만 합성 관계는 런타임에 동적으로 변경이 가능하다.

  • 위와 같은 특성 때문에 상속 대신 합성을 사용하면 변경하기 쉽고 유연한 설계를 얻을 수 있다.

  • 코드 재사용을 위해서는 객체 합성이 클래스 상속보다 더 좋은 방법이다.

  • 상속합성재사용 대상이 다르다.

  • 상속부모 클래스 안에 구현된 코드 자체를 재사용한다.

  • 합성포함되는 객체의 퍼블릭 인터페이스를 재사용한다.

  • 상속 대신 합성을 사용하면 구현에 대한 의존성을 인터페이스에 대한 의존성으로 변경할 수 있다. 즉, 클래스 사이의 높은 결합도를 객체 사이의 낮은 결합도로 대체할 수 있다.

  • 상속을 받으면 부모 클래스의 내부가 자식 클래스에 공개되기 때문에 화이트박스 재사용이라 부른다.

  • 합성은 새로운 기능을 위해 객체들을 합성한다. 이런 스타일의 재사용을 블랙박스 재사용이라 부르는데, 객체의 내부는 공개되지 않고 인터페이스를 통해서만 재사용되기 때문이다.

  • 상속을 이용하여 코드 재사용을 한다면 불필요한 인터페이스 상속 문제, 메서드 오버라이딩 오작동 문제, 부모 클래스와 자식 클래스의 동시 수정 문제 와 같은 변경에 불안정한다양한 문제들이 발생힌다.

  • 상속으로 인해서 결합도가 높아지면 코드를 수정 및 추가하는 데 필요한 작업의 양이 과도하게 늘어나는 경향이 있다.

  • 작은 기능들을 조합해서 더 큰 기능을 수행하는 객체를 만들어야 하는 경우, 하나의 기능을 추가하거나 수정하기 위해 불필요하게 많은 수의 클래스를 추가하거나 수정해야 한다.

  • 합성을 사용하면 상속으로 인해 발생하는 클래스의 증가와 중복 코드 문제를 간단하게 해결할 수 있다.

  • 다양한 조합 가능한 기능을 위해 상속을 사용한다면, 모든 가능한 조합별로 자식 클래스를 하나씩 추가해야 한다.

  • 상속의 남용으로 하나의 기능을 추가하기 위해 필요 이상으로 많은 수의 클래스를 추가하는 경우를 가리켜 클래스 폭발/조합 폭발문제라고 부른다.

  • 클래스 폭발 문제는 자식 클래스가 부모 클래스의 내부 구현에 강하게 결합되도록 강요하는 상속의 근본적인 한계 때문에 발생하는 문제다.

  • 컴파일타임에 결정된 자식 클래스와 부모 클래스 사이의 관계는 변경될 수 없기 때문에 자식 클래스와 부모 클래스의 다양한 조합이 필요한 상황에서 유일한 해결 방법은 조합의 수 만큼이나 새로운 클래스를 추가하는 것 뿐이다.

  • 상속 관계는 컴파일타임에 결정되고 고정되기 때문에 런타임에 변경할 수 없다. 따라서 여러 기능을 조합해야 하는 설계에서 상속을 이용하면 모든 조합 가능한 경우의 수 만큼 클래스를 추가해야 한다. 이것이 바로 클래스 폭발/조합의 폭발문제다.

  • 합성컴파일타임 관게를 런타임 관계로 변경함으로써 문제를 해결한다. 합성을 사용하면 구현이 아닌 퍼블릭 인터페이스에 대해서만 의존할 수 있기 때문에 런타임에 객체의 관계를 변경할 수 있다.

  • 컴파일타임 의존성과 런타임 의존성의 거리가 멀수록 설계가 유연해진다.

  • 상속과 달리 합성 관계런타임에 동적으로 변경할 수 있다. 클래스 폭발 문제를 해결하기 위해 합성을 사용하는 이유는 런타임에 객체 사이의 의존성을 자유롭게 변경할 수 있기 때문이다. 즉, 하나의 코드 구조에서 다양한 협력이 가능하기 떄문이다.

  • 합성을 사용하면 구현 시점에 관계를 고정시킬 필요가 없으며, 실행 시점에 관계를 유연하게 변경할 수 있게 된다.

  • 상속이 조합의 결과를 개별 클래스 안으로 밀어넣는 방법이라면(상속을 이용해서 조합 결과를 하나의 클래스로 만듦), 합성은 조합을 구성하는 요소들을 개별 클래스로 구현한 뒤 실행 시점에 인스턴스를 조립하는 방법을 사용하는 것이라고 할 수 있다.

  • 컴파일 의존성에 속박되지 않고 다양한 방식의 런타임 의존성(협력)을 구성할 수 있다는 것이 합성이 제공하는 가장 커다란 장점이다.


코드 재사용을 위한 상속과 합성의 특성

코드 재사용에 있어서 상속은 간편한 방법일지는 몰라도 좋은 방법은 아니다.변경에 대한 불안정성유연하지 못한 설계가 가장 큰 원인인데, 이러한 문제점을 합성이 어떻게 해결하는지 하나씩 살펴보자.

의존 대상과의 결합도

상속의 재사용 대상은 부모 클래스의 코드이기 때문에 의존 대상이 부모 클래스이다. 반면 합성의 재사용 대상은 포함되는 객체의 퍼블릭 인터페이스이기 때문에 의존 대상이 포함되는 객체이다.

상속은 근본적으로 부모 클래스의 코드가 정적으로 자식 클래스에 결합되기 때문에 결합도가 높다. 이에 따라 부모 클래스가 변경되면 자식 클래스가 영향을 고스란히 받는 ‘취약한 기반 클래스 문제’가 발생하게 된다.

위와 같은 문제 때문에 코드 재사용을 위한 상속을 위해서는 부모 클래스의 각 API가 어떤 식으로 호출이 되는지, 재사용하려는 API가 부모 클래스의 내부 구현과 어떤 연관이 있는지, 오버라이딩 하려는 API가 부모 클래스의 내부 구현과 어떤 연관이 있는지, 재사용을 한다면 기존 코드에 영향을 미치지는 않는지 등 부모 클래스의 상세한 내부 구현에 대한 지식이 필요하다.

결합도는 의존 대상에 대한 지식의 양으로 결정된다. 라고 하였다. 이로 비추어 봤을 때 코드 재사용을 위한 상속부모 클래스의 내부 구현에 대한 상세한 지식이 필요하기 때문에 변경에 불안정하고 결합도가 높다.

반면에 합성은 포함되는 객체의 내부 구현에 대한 지식이 필요가 없다. 단순히 포함되는 객체가 메시지를 이해하고 원하는 결과를 응답할 것이라고 믿고 메시지를 전송하기만 하면 된다. 따라서 합성은 객체의 퍼블릭 인터페이스에 의존하기 때문에 결합도가 낮다.

의존성 해결(결정) 시점

상속의 의존성은 컴파일 타임에 결정되고 고정된다. 반면에 합성의 의존성은 런타임에 결정되고 변경 가능하다.

의존성 해결의 시점이 컴파일타임이냐, 런타임이냐 또한 의존성이 정적이냐 동적이냐는 설계의 유연성에 있어서 아주 중요한 차이를 드러낸다.

상속과 같이 컴파일타임에 의존성이 해결되는 경우, 런타임에 변경이 불가능하다. 즉, 정적으로 부모 클래스와 자식 클래스가 결합되기 때문에 필요한 기능의 조합의 수 만큼 상속 개체가 필요하다. 결과적으로 가능한 기능 조합 경우의 수 만큼 클래스가 추가되어야 한다.

반면 합성과 같이 런타임에 의존성이 해결되는 경우, 런타임에 의존성 변경이 가능하다. 즉, 동적으로 의존성 변경이 가능하기 때문에 개별 기능 단위로 클래스를 구현하여 런타임에 해당 인스턴스들을 조합 할 수 있다.

이러한 차이를 만드는 근본적인 이유는 상속부모 클래스와 자식 클래스가 정적으로 컴파일 타임에 의존성이 해결되고, 합성포함되는 객체와 동적으로 런타임에 의존성이 해결되기 때문이다.

결론

코드 재사용을 위해서는 상속보다 합성이 훨씬 더 우아한 방법이다.

그리고 그 차이는 크게 구현에 결합하느냐 퍼블릭 인터페이스에 결합하느냐, 컴파일타임 의존성과 런타임 의존성으로 부터 생겨난다.

이에 따라 상속부모 클래스의 변경에 자식 클래스가 영항을 받는 ‘취약한 기반 클래스 문제’를 갖고, 모든 가능한 조합의 기능을 하나의 클래스로 상속하여 구현해야 하는 낮은 유연성을 갖는다.

반면 합성포함되는 객체의 퍼블릭 인터페이스가 변경될 경우에만 영향을 받고, 개별 기능을 갖는 객체들을 런타임에 조합 가능한 유연한 설계를 갖는다.

나머지

챕터 11의 주 내용은 코드 재사용을 위해 상속을 사용한 설계의 문제점들과 합성이 이를 어떻게 해결하는지에 대한 내용이다.

그 외 내용들은 코드 예시를 통한 구체적인 예시이기 때문에 본문과 다르지 않으므로 본문을 직접 참고하자.

참고 및 출처

  • 오브젝트