Ch11 합성과 유연한 설계
들어가며
코드 재사용
과 관련하여 상속
은 간편한 방법이지만 부모 클래스와 자식 클래스의 결합
때문에 변경에 불안정하고 유연하지 못하다.
이번 장에서는 상속
대신 합성
을 사용함으로써 어떻게 유연한 설계
를 얻을 수 있는지에 대해서 알아본다.
핵심.
-
상속
이 부모 클래스와 자식 클래스를 연결하여부모 클래스의 코드를 재사용
하는데 반해합성
은 전체를 표현하는 객체가 부분을 표현하는 객체를포함
하여 부분 객체의 코드를 재사용한다. -
상속
에서 부모 클래스와 자식 클래스 사이의의존성
은컴파일 타임
에 해결되지만합성
에서 두 객체 사이의 의존성은런타임
에 해결된다. -
상속
은 부모 클래스의내부 구현
에 대해 상세히 알아야 하기 때문에 자식 클래스와 부모 클래스 사이의 결합도가 높을 수 밖에 없다. -
합성
은 구현에 의존하지 않는다는 점에서 상속과 다르다.합성
은 내부에 포함되는 객체의구현이 아닌 퍼블릭 인터페이스에 의존
한다. -
합성
을 이용하면 포함된 객체의 내부 구현이 변경되더라도 영향을 최소화 할 수 있기 때문에변경에 더 안정적
인 코드를 얻을 수 있다. -
상속 관계는 클래스 사이의 정적인 관계인 데 비해 합성 관계는 객체 사이의 동적인 관계다.
-
코드 작성 시점에 결정한 상속 관계는 변경이 불가능하지만 합성 관계는 런타임에 동적으로 변경이 가능하다.
-
위와 같은 특성 때문에 상속 대신 합성을 사용하면 변경하기 쉽고 유연한 설계를 얻을 수 있다.
-
코드 재사용을 위해서는 객체 합성이 클래스 상속보다 더 좋은 방법이다.
-
상속
과합성
은 재사용 대상이 다르다. -
상속
은부모 클래스 안에 구현된 코드 자체를 재사용
한다. -
합성
은포함되는 객체의 퍼블릭 인터페이스를 재사용
한다. -
상속
대신합성
을 사용하면 구현에 대한 의존성을 인터페이스에 대한 의존성으로 변경할 수 있다. 즉, 클래스 사이의 높은 결합도를 객체 사이의 낮은 결합도로 대체할 수 있다. -
상속
을 받으면 부모 클래스의 내부가 자식 클래스에 공개되기 때문에화이트박스 재사용
이라 부른다. -
합성
은 새로운 기능을 위해 객체들을 합성한다. 이런 스타일의 재사용을블랙박스 재사용
이라 부르는데, 객체의 내부는 공개되지 않고 인터페이스를 통해서만 재사용되기 때문이다. -
상속
을 이용하여코드 재사용
을 한다면 불필요한 인터페이스 상속 문제, 메서드 오버라이딩 오작동 문제, 부모 클래스와 자식 클래스의 동시 수정 문제 와 같은변경에 불안정한다양한 문제
들이 발생힌다. -
상속
으로 인해서 결합도가 높아지면 코드를 수정 및 추가하는 데 필요한 작업의 양이 과도하게 늘어나는 경향이 있다. -
작은 기능들을 조합해서 더 큰 기능을 수행하는 객체를 만들어야 하는 경우, 하나의 기능을 추가하거나 수정하기 위해 불필요하게 많은 수의 클래스를 추가하거나 수정해야 한다.
-
합성
을 사용하면상속
으로 인해 발생하는 클래스의 증가와 중복 코드 문제를 간단하게 해결할 수 있다. -
다양한 조합 가능한 기능을 위해
상속
을 사용한다면, 모든 가능한 조합별로 자식 클래스를 하나씩 추가해야 한다. -
상속의 남용
으로 하나의 기능을 추가하기 위해 필요 이상으로 많은 수의 클래스를 추가하는 경우를 가리켜클래스 폭발/조합 폭발
문제라고 부른다. -
클래스 폭발
문제는 자식 클래스가 부모 클래스의 내부 구현에 강하게 결합되도록 강요하는 상속의 근본적인 한계 때문에 발생하는 문제다. -
컴파일타임에 결정된 자식 클래스와 부모 클래스 사이의 관계
는 변경될 수 없기 때문에 자식 클래스와 부모 클래스의 다양한 조합이 필요한 상황에서 유일한 해결 방법은 조합의 수 만큼이나 새로운 클래스를 추가하는 것 뿐이다. -
상속 관계는 컴파일타임에 결정되고 고정
되기 때문에 런타임에 변경할 수 없다. 따라서여러 기능을 조합해야 하는 설계
에서 상속을 이용하면모든 조합 가능한 경우의 수 만큼 클래스를 추가
해야 한다. 이것이 바로클래스 폭발/조합의 폭발
문제다. -
합성
은컴파일타임 관게를 런타임 관계로 변경
함으로써 문제를 해결한다. 합성을 사용하면 구현이 아닌 퍼블릭 인터페이스에 대해서만 의존할 수 있기 때문에 런타임에 객체의 관계를 변경할 수 있다. -
컴파일타임 의존성과 런타임 의존성의 거리가 멀수록 설계가 유연해진다.
-
상속
과 달리합성 관계
는런타임에 동적으로 변경
할 수 있다. 클래스 폭발 문제를 해결하기 위해합성
을 사용하는 이유는런타임에 객체 사이의 의존성을 자유롭게 변경할 수 있기 때문
이다. 즉, 하나의 코드 구조에서 다양한 협력이 가능하기 떄문이다. -
합성을 사용하면 구현 시점에 관계를 고정시킬 필요가 없으며, 실행 시점에 관계를 유연하게 변경할 수 있게 된다.
-
상속
이 조합의 결과를 개별 클래스 안으로 밀어넣는 방법이라면(상속을 이용해서조합 결과를 하나의 클래스로 만듦
),합성
은 조합을 구성하는 요소들을 개별 클래스로 구현한 뒤실행 시점에 인스턴스를 조립하는 방법을 사용
하는 것이라고 할 수 있다. -
컴파일 의존성에 속박되지 않고 다양한 방식의 런타임 의존성(협력)을 구성할 수 있다는 것이 합성이 제공하는 가장 커다란 장점이다.
코드 재사용을 위한 상속과 합성의 특성
코드 재사용
에 있어서 상속
은 간편한 방법일지는 몰라도 좋은 방법은 아니다.변경에 대한 불안정성
과 유연하지 못한 설계
가 가장 큰 원인인데, 이러한 문제점을 합성
이 어떻게 해결하는지 하나씩 살펴보자.
의존 대상과의 결합도
상속
의 재사용 대상은 부모 클래스의 코드
이기 때문에 의존 대상이 부모 클래스
이다. 반면 합성
의 재사용 대상은 포함되는 객체의 퍼블릭 인터페이스
이기 때문에 의존 대상이 포함되는 객체
이다.
상속
은 근본적으로 부모 클래스의 코드가 정적으로 자식 클래스에 결합되기 때문에 결합도가 높다. 이에 따라 부모 클래스가 변경되면 자식 클래스가 영향을 고스란히 받는 ‘취약한 기반 클래스 문제’가 발생하게 된다.
위와 같은 문제 때문에 코드 재사용을 위한 상속
을 위해서는 부모 클래스의 각 API가 어떤 식으로 호출이 되는지, 재사용하려는 API가 부모 클래스의 내부 구현과 어떤 연관이 있는지, 오버라이딩 하려는 API가 부모 클래스의 내부 구현과 어떤 연관이 있는지, 재사용을 한다면 기존 코드에 영향을 미치지는 않는지 등 부모 클래스의 상세한 내부 구현에 대한 지식
이 필요하다.
결합도는 의존 대상에 대한 지식의 양으로 결정된다. 라고 하였다. 이로 비추어 봤을 때 코드 재사용을 위한 상속
은 부모 클래스의 내부 구현에 대한 상세한 지식
이 필요하기 때문에 변경에 불안정
하고 결합도가 높다.
반면에 합성
은 포함되는 객체의 내부 구현
에 대한 지식이 필요가 없다. 단순히 포함되는 객체가 메시지를 이해하고 원하는 결과를 응답
할 것이라고 믿고 메시지를 전송하기만 하면 된다. 따라서 합성은 객체의 퍼블릭 인터페이스에 의존
하기 때문에 결합도가 낮다.
의존성 해결(결정) 시점
상속
의 의존성은 컴파일 타임
에 결정되고 고정된다. 반면에 합성
의 의존성은 런타임
에 결정되고 변경 가능하다.
의존성 해결의 시점이 컴파일타임
이냐, 런타임
이냐 또한 의존성이 정적
이냐 동적
이냐는 설계의 유연성
에 있어서 아주 중요한 차이를 드러낸다.
상속
과 같이 컴파일타임
에 의존성이 해결되는 경우, 런타임
에 변경이 불가능하다. 즉, 정적
으로 부모 클래스와 자식 클래스가 결합되기 때문에 필요한 기능의 조합의 수 만큼 상속 개체가 필요하다. 결과적으로 가능한 기능 조합 경우의 수 만큼 클래스가 추가되어야 한다.
반면 합성
과 같이 런타임
에 의존성이 해결되는 경우, 런타임에 의존성 변경이 가능
하다. 즉, 동적
으로 의존성 변경이 가능하기 때문에 개별 기능 단위로 클래스를 구현하여 런타임에 해당 인스턴스들을 조합 할 수 있다.
이러한 차이를 만드는 근본적인 이유는 상속
은 부모 클래스와 자식 클래스가 정적으로 컴파일 타임에 의존성이 해결
되고, 합성
은 포함되는 객체와 동적으로 런타임에 의존성이 해결
되기 때문이다.
결론
코드 재사용
을 위해서는 상속
보다 합성
이 훨씬 더 우아한 방법이다.
그리고 그 차이는 크게 구현에 결합하느냐 퍼블릭 인터페이스에 결합하느냐
, 컴파일타임 의존성과 런타임 의존성
으로 부터 생겨난다.
이에 따라 상속
은 부모 클래스의 변경에 자식 클래스가 영항을 받는 ‘취약한 기반 클래스 문제’를 갖고, 모든 가능한 조합의 기능을 하나의 클래스로 상속하여 구현해야 하는 낮은 유연성을 갖는다.
반면 합성
은 포함되는 객체의 퍼블릭 인터페이스가 변경될 경우에만 영향을 받고, 개별 기능을 갖는 객체들을 런타임에 조합 가능한 유연한 설계를 갖는다.
나머지
챕터 11의 주 내용은 코드 재사용을 위해 상속
을 사용한 설계의 문제점들과 합성
이 이를 어떻게 해결하는지에 대한 내용이다.
그 외 내용들은 코드 예시
를 통한 구체적인 예시이기 때문에 본문과 다르지 않으므로 본문을 직접 참고하자.
참고 및 출처
- 오브젝트