본문 바로가기

기술서적 정리/오브젝트 - 코드로 이해하는 객체지향 설계

11 CHAPTER 합성과 유연한 설계

https://wikibook.co.kr/object/

 

오브젝트: 코드로 이해하는 객체지향 설계

역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라! 객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두 번째 걸음은 객체를

wikibook.co.kr

 

 

합성은 전체를 표현하는 객체가 부분을 표현하는 객체를 포함해서 부분 객체의 코드를 재사용한다.

합성에서 두 객체 사이의 의존성은 런타임에 해결된다.

상속 관계는 is-a 관계라고 부르고 합성 관계는 has-a 관계라고 부른다.

 

합성은 내부에 포함되는 객체의 구현이 아닌 퍼블릭 인터페이스에 의존한다.

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

 

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

코드 작성 시점에 결정한 상속 관계는 변경이 불가능하지만 합성 관계는 실행 시점에 동적으로 변경할 수 있다.

따라서 상속 대신 합성을 사용하면 변경하기 쉽고 유연한 설계를 얻을 수 있다.

 

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

 

상속은 부모 클래스 안에 구현된 코드 자체를 재사용하지만 합성은 포함되는 객체의 퍼블릭 인터페이스를 재사용한다.

따라서 상속 대신 합성을 사용하면 구현에 대한 의존성을 인터페이스에 대한 의존성으로 변경할 수 있다.

다시 말해서 클래스 사이의 높은 결합도를 객체 사이의 낮은 결합도로 대체할 수 있는 것이다.





◈ 상속을 합성으로 변경하기

코드 재사용을 위해 상속을 남용했을 때 직면할 수 있는 세 가지 문제점.

1. 불필요한 인터페이스 상속 문제.

2. 메서드 오버라이딩의 오작용 문제.

3. 부모 클래스와 자식 클래스의 동시 수정 문제.



합성을 사용하면 상속이 초래하는 세 가지 문제점을 해결할 수 있다.

상속을 합성으로 바꾸는 방법은 자식 클래스에 선언된 상속 관계를 제거하고 부모 클래스의 인스턴스를 자식 클래스의 인스턴스 변수로 선언하면 된다.

 

오퍼레이션을 오버라이딩한 인스턴스 메서드에서 내부의 인스턴스에게 동일한 메서드 호출을 그대로 전달한다.

이를 포워딩이라고 부르고 동일한 메서드를 호출하기 위해 추가된 메서드를 포워딩 메서드라고 부른다.

포워딩은 기존 클래스의 인터페이스를 그대로 외부에 제공하면서 구현에 대한 결합 없이 일부 작동 방식을 변경하고 싶을 경우에 사용할 수 있는 유용한 기법이다.





◈ 상속으로 인한 조합의 폭발적인 증가

요구사항을 구현하는 데 가장 큰 장벽은 기본 정책과 부가 정책의 조합 가능한 수가 매우 많다는 것이다.

 

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

 

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

 

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

 

클래스 폭발 문제는 새로운 기능을 추가할 때뿐만 아니라 기능을 수정할 때도 문제가 된다.





◈ 합성 관계로 변경하기

합성은 컴파일 타임 관계를 런타임 관계로 변경함으로써 클래스 폭발 문제를 해결한다.

합성을 사용하면 구현이 아닌 퍼블릭 인터페이스에 대해서만 의존할 수 있기 때문에 런타임에 객체의 관계를 변경할 수 있다.

 

합성을 사용하면 컴파일타임 의존성과 런타임 의존성을 다르게 만들 수 있다.

클래스 폭발 문제를 해결하기 위해 합성을 사용하는 이유는 런타임에 객체 사이의 의존성을 자유롭게 변경할 수 있기 때문이다.

 

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

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

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



기본 정책과 부가 정책 합성하기

합성을 처음 보게되면 상속을 사용한 설계보다 복잡하고 정해진 규칙에 따라 객체를 생성하고 조합해야 하기 때문에 처음에는 코드를 이해하기 어려울 수도 있다.

하지만 일단 설계에 익숙해지고 나면 객체를 조합하고 사용하는 방식이 상속을 사용한 방식보다 더 예측 가능하고 일관성이 있다는 사실을 알게 될 것이다.

 

합성의 진가는 새로운 클래스를 추가하거나 수정하는 시점이 돼서야 비로소 알 수 있다.

 


새로운 정책 추가하기

합성을 기반으로 한 설계에서는 새로운 부가 정책을 추가하기 위해서 클래스 ‘하나’만 추가한 후 원하는 방식으로 조합하면 된다.

 

우리는 오직 하나의 클래스만 추가하고 런타임에 필요한 정책들을 조합해서 원하는 기능을 얻을 수 있다.

 

필요한 조합의 수만큼 매번 새로운 클래스를 추가해야 했던 상속과 비교했을 때 많은 사람들이 코드 재사용을 위해 상속보다 합성을 사용하라고 하는지 그 이유를 이해할 수 있을 것이다.

 


객체 합성이 클래스 상속보다 더 좋은 방법이다.

코드를 재사용하면서도 건전한 결합도를 유지할 수 있는 더 좋은 방법은 합성을 이용하는 것이다.

상속이 구현을 재사용하는 데 비해 합성은 객체의 인터페이스를 재사용한다.

 

상속은 사용해서는 안되는 것인가?

이 의문에 대답하기 위해서는 먼저 상속을 구현 상속과 인터페이스 상속의 두 가지로 나눠야한다.

그리고 지금까지 알아본 상속에 대한 모든 단점들은 구현 상속에 국한된다.

 

 

 

 

◈ 믹스인

믹스인은 객체를 생성할 때 코드 일부를 클래스 안에 섞어 넣어 재사용하는 기법을 가리키는 용어다.

합성이 실행 시점에 객체를 조합하는 재사용 방법이라면 믹스인은 컴파일 시점에 필요한 코드 조각을 조합하는 재사용 방법이다.

 

믹스인과 상속이 유사한 것처럼 보이겠지만 믹스인은 상속과 다르다.

믹스인은 말 그대로 코드를 다른 코드 안에 섞어 넣기 위한 방법이다.

상속이 클래스와 클래스 사이의 관계를 고정시키는 데 비해 믹스인은 유연하게 관계를 재구성할 수 있다.

믹스인은 코드 재사용에 특화된 방법이면서도 상속과 같은 결합도 문제를 초래하지 않는다.

 

상속은 정적이지만 믹스인은 동적이다.

상속은 부모 클래스와 자식 클래스의 관계를 코드를 작성하는 시점에 고정시켜 버리지만 믹스인은 제약을 둘뿐 실제로 어떤 코드에 믹스인될 것인지를 결정하지 않는다.

 

믹스인은 상속보다는 합성과 유사하다.

합성은 독립적으로 작성된 객체들을 실행 시점에 조합해서 더 큰 기능을 만들어내는 데 비해 믹스인은 독립적으로 작성된 트레이트와 클래스를 코드 작성 시점에 조합해서 더 큰 기능을 만들어낼 수 있다.

 

믹스인을 사용하더라도 상속에서 클래스의 숫자가 기하급수적으로 늘어나는 클래스 폭발 문제는 여전히 남아 있는 것이 아니냐고 반문할지 모르겠다.

사실 클래스 폭발 문제의 단점은 클래스가 늘어난다는 것이 아니라 클래스가 늘어날수록 중복 코드도 함께 기하급수적으로 늘어난다는 점이다.

믹스인에는 이런 문제가 발생하지 않는다.

 

클래스를 만들어야하는 것이 불만이라면 클래스를 만들지 않고 인스턴스를 생성할 때 트레이트를 믹스인할 수도 있다.

이 방법은 믹스인한 인스턴스가 오직 한 군데에서만 필요한 경우에 사용할 수 있다.

하지만 코드 여러 곳에서 동일한 트레이트를 믹스인해서 사용해야 한다면 명시적으로 클래스를 정의하는 것이 좋다.



쌓을 수 있는 변경

전통적으로 믹스인은 특정한 클래스의 메서드를 재사용하고 기능을 확장하기 위해 사용돼 왔다.

 

믹스인은 상속 계층 안에서 확장한 클래스보다 더 하위에 위치하게 된다.

다시 말해서 믹스인은 대상 클래스의 자식 클래스처럼 사용될 용도로 만들어지는 것이다.

믹스인을 추상 서브클래스라고 부르기도 한다.

 

믹스인을 사용하면 특정한 클래스에 대한 변경 또는 확장을 독립적으로 구현한 후 필요한 시점에 차례대로 추가할 수 있다.

믹스인의 이런한 특징을 쌓을 수 있는 변경이라고 부른다.