책임 주도 설계를 향해책임주도 설계의 흐름책임 할당을 위한 GRASP 패턴도메인 개념에서 출발하기정보 전문가에게 책임을 할당하라높은 응집도와 낮은 결합도창조자에게 객체 생성 책임을 할당하라구현을 통한 검증클래스 응집도 판단하기다형성을 통해 분리하기변경과 유연성책임 주도 설계의 대안메서드 응집도객체를 자율적으로 만들자
책임 주도 설계를 향해
- 데이터 중심의 설계에서 책임 주도 설계로 전환하기 위해서는 다음의 두 가지 원칙을 따라야 함
- 데이터보다 행동을 먼저 결정하라
- 협력이라는 문맥 안에서 책임을 결정하라
- 협력에 적합한 책임을 수확하기 위해서는 객체를 결정한 후에 메시지를 선택하는 것이 아니라 메시지를 결정한 후에 객체를 선택해야 함. 메시지가 존재하기 때문에 그 메시지를 처리할 객체가 필요한 것임. 객체가 메시지를 선택하는 것이 아닌, 메시지가 객체를 선택하게 해야 한다.
- 메시지를 먼저 결정하기 때문에 메시지 송신자는 메시지 수신자에 대한 어떠한 가정도 할 수 없다. 메시지 전송자의 관점에서 메시지 수신자가 깔끔하게 캡슐화되는 것
책임주도 설계의 흐름
- 시스템이 사용자에게 제공해야 하는 기능인 시스템 책임을 파악
- 시스템 책임을 더 작은 책임으로 분할
- 분할된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아 책임을 할당
- 객체가 책임을 수행하는 도중 다른 객체의 도움이 필요한 경우 이를 책임질 적절한 객체 또는 역할을 찾는다.
- 해당 객체 또는 역할에게 책임을 할당 함으로써 두 객체가 협력하게 한다.
책임주도 설계의 핵심은 책임을 결정한 후에 책임을 수행할 객체를 결정하는 것이다.
책임 할당을 위한 GRASP 패턴
도메인 개념에서 출발하기
- 설계를 시작하기 전에 도메인에 대한 개략적인 모습을 그려 보는 것이 유용하다
- 도메인 안에는 무수히 많은 개념들이 존재하며 이 도메인 개념들을 책임 할당의 대상으로 사용하면 코드에 도메인의 모습을 투영하기가 좀 더 수월해진다.

- 설계를 시작하는 단계에서는 개념들의 의미와 관계가 정확하거나 완벽할 필요가 없다. 단지 우리에게는 출발점이 필요할 뿐이다. 이 단계에서는 책임을 할당받을 객체들의 종류와 관계에 대한 유용한 정보를 제공할 수 있다면 충분
올바른 도메인 모델이란 존재하지 않는다
5장에서의 도메인 모델은 2장에서 설명한 도메인 모델과 약간 다르다.
2장에서는 할인 정책이라는 개념이 하나의 독립적인 개념으로 분리되어 있지만 위에서는 영화의 종류로 표현되어 있다. 어떤 쪽이 올바른 도메인 모델인가? 만약 두 도메인 모델 모두 올바른 구현을 이끌어 낼 수만 있다면 정답은 ‘둘 다’ 다.
도메인 모델을 기반으로 코드를 구현 하면서 얻게되는 통찰로 인해 도메인에 대한 개념을 또 바꿀수가 있기 때문이다. 필요한 것은 도메인을 그대로 투영한 모델이 아닌 구현에 도움이 되는 모델이다.
정보 전문가에게 책임을 할당하라
- 책임 주도 설계 방식의 첫 단계는 애플리케이션이 제공해야 하는 기능을 애플리케이션의 책임으로 생각하는 것
- 이 책임을 애플리케이션에 대해 전송된 메시지로 간주하고 이 메시지를 책임질 첫 번째 객체를 선택하는 것으로 설계를 시작함
사용자에게 제공해야 할 기능 : 영화 예매
메시지 : 영화 예매하라
메시지 수신 객체는 누구인가? ⇒ 객체에게 책임을 할당하는 첫 번째 원칙은 책임을 수행할 정보를 알고 있는 객체에게 책임을 할당하는 것 [INFORMATION EXPERT(정보 전문가) 패턴]
- 정보 전문가 패턴은 책임을 수행하는 데 필요한 정보를 가지고 있는 객체에게 책임을 할당하라는 것
- 정보를 가지고 있다는 것 ≠ 정보를 저장하는 것
- 해당 정보를 제공할 수 있는 다른 객체를 알고 있거나 필요한 정보를 계산해서 제공할 수 있음
높은 응집도와 낮은 결합도
- 설계는 트레이드오프 활동이라는 것을 기억하라. 동일한 기능을 구현할 수 있는 무수히 많은 설계가 존재한다. 따라서 실제로 설계를 진행하다 보면 몇 가지 설계 중 한가지를 선택해야 하는 경우가 빈번하게 발생한다.
- 예로, 위의 영화 예매 시스템에서 할인 요금 계산하기 위해 Movie가 DiscountCondition에 할인 여부를 판단하라는 메시지를 전송함
- 이 설계의 대안으로 Screening이 직접 DiscountCondition에 할인 여부 판단하라고 메시지 보내고, Movie에게 가격 계산하라는 식으로 메시지 보낸다면?
- 기능은 동일하지만, 도메인 개념을 참고해보면 Movie가 DiscountCondition의 리스트를 갖고 있기에 Movie가 메시지를 보내는 편이 결합도를 낮추게 된다.(Screening이 굳이 DiscountCondition을 몰라도 되기에) ⇒
LOW COUPLING
- 또한, Screening의 가장 중요한 책임은 예매를 생성하는 것인데, 만약 Screening이 DiscountCondition과 협력해야 한다면 Screening은 영화 요금 계산과 관련된 책임 일부를 떠안아야 할 것임. 이 경우 Screening은 DiscountCondition이 할인 여부를 판단할 수 있고 Movie가 이 할인 여부를 필요로 한다는 사실 역시 알고 있어야 한다. ⇒ 예매 요금 계산 방식이 변경될 경우 Screening도 함께 변경 → 응집도가 낮아짐(
HIGH COHESION
X
) - Movie 의 주된 책임이 영화 요금을 계산하는 것이기에 Discount Condition과 협력하는 것이 응집도에 아무런 해도 끼치지 않는다.

창조자에게 객체 생성 책임을 할당하라
CREATOR 패턴
객체 A를 생성해야 할 때 어떤 객체에게 객체 생성 책임을 할당해야 하는가? 아래 조건을 최대한 많이 만족하는 B에게 객체 생성 책임을 할당하라
- B가 A 객체를 포함하거나 참조한다
- B가 A 객체를 기록한다.
- B가 A 객체를 긴밀하게 사용한다.
- B가 A 객체를 초기화하는 데 필요한 데이터를 가지고 있다
구현을 통한 검증
클래스 응집도 판단하기
클래스가 다음과 같은 징후로 몸살을 앓고 있다면 클래스의 응집도는 낮은 것이다.
- 클래스가 하나 이상의 이유로 변경돼야 한다면 응집도가 낮은 것이다. 변경의 이유를 기준으로 클래스를 분리하라
- 클래스의 인스턴스를 초기화하는 시점에 경우에 따라 서로 다른 속성들을 초기화하고 있다면 응집도가 낮은 것이다. 초기화되는 속성의 그룹을 기준으로 클래스를 분리하라
- 메서드 그룹이 속성 그룹을 사용하는지 여부로 나뉜다면 응집도가 낮은 것이다. 이들 그룹을 기준으로 클래스를 분리하라
일반적으로 응집도가 낮은 클래스는 이 세 가지 문제를 동시에 가지는 경우가 대부분이다.
다형성을 통해 분리하기
- 객체의 암시적인 타입에 따라 행동을 분기해야 한다면 암시적인 타입을 명시적인 클래스로 정의하고 행동을 나눔으로써 응집도 문제를 해결할 수 있다.
- 다시 말해 객체의 타입에 따라 변하는 행동이 있다면 타입을 분리하고 변화하는 행동을 각 타입의 책임으로 할당하라는 것이다 ⇒
POLYMORPHISM
(다형성) 패턴 - 조건에 따른 변화는 프로그램의 기본 논리다. 프로그램을 if ~ else 또는 switch ~ case 등의 조건 논리를 사용해서 설계한다면 새로운 변화가 일어난 경우 조건 논리를 수정해야 한다. ⇒ 프로그램 수정이 어렵고 변경에 취약하게 만든다
변경과 유연성
- 설계를 주도하는 것은 변경이다. 예를 들어, 영화에 설정된 할인 정책을 실행 중에 변경할 수 있어야 한다는 요구사항이 추가됐다고 가정한다면, 현재의 설계에서는 할인 정책을 구현하기 위해 상속을 이용하고 있기 때문에 실행 중에 영화의 할인 정책을 변경하기 위해서는 새로운 인스턴스를 생성한 후 필요한 정보를 복사해야 한다. ⇒ 개념적으로는 동일한 객체를 가리키지만 물리적으로는 서로 다른 객체이기에 혼란함

- 해결 방법은 상속 대신 합성을 사용하는 것 ( 아래 그림과 같이 Movie의 상속 계층 안에 구현된 할인 정책을 독립적인 DiscountPolicy로 분리한 후 Movie에 합성시키면 유연한 설계가 완성됨 )

- 위의 예는 유연성에 대한 압박이 설계에 어떤 영향을 미치는지를 잘 보여준다. 실제로 유연성은 의존성 관리의 문제다. 요소들 사이의 의존성의 정도가 유연성의 정도를 결정한다.
책임 주도 설계의 대안
- 책임 주도 설계에 익숙해지기 위해서는 부단한 노력과 시간이 필요하다
- 책임과 객체 사이에서 방황할 때 돌파구를 찾기 위해 선택하는 방법은 최대한 빠르게 목적한 기능을 수행하는 코드를 작성하는 것이다.
- 아무것도 없는 상태에서 책임과 협력에 관해 고민하기 보다는 일단 실행되는 코드를 얻고 난 후에 코드 상에 명확하게 드러나는 책임들을 올바른 위치로 이동시키는 것
메서드 응집도
- 응집도가 낮은 메서드는 로직의 흐름을 이해하기 위해 주석이 필요한 경우가 대부분이다. 메서드가 명령문들의 그룹으로 구성되고 각 그룹에 주석을 달아야 할 필요가 있다면 그 메서드의 응집도는 낮은 것이다.
- 주석을 추가하는 대신 메서드를 작게 분해해서 각 메서드의 응집도를 높여라
- 클래스의 응집도와 마찬가지로 메서드의 응집도를 높이는 이유도 변경과 관련이 깊다. 응집도 높은 메서드는 변경되는 이유가 단 하나여야 한다. 클래스가 작고, 목적이 명확한 메서드들로 구성돼 있다면 변경을 처리하기 위해 어떤 메서드를 수정해야 하는지를 쉽게 판단할 수 있다.
- 또한 메서드의 크기가 작고 목적이 분명하기 때문에 재사용하기도 쉽다.
나는 다음과 같은 이유로 짧고, 이해하기 쉬운 이름으로 된 메서드를 좋아한다. 첫째, 메서드가 잘게 나눠져 있을 때 다른 메서드에서 사용될 확률이 높아진다. 둘째, 고수준의 메서드를 볼 때 일련의 주석을 읽는 것 같은 느낌이 들게 할 수 있다. 또한 메서드가 잘게 나눠져 있을 때 오버라이딩하는 것도 훨씬 쉽다. 만약 큰 메서드에 익숙해져 있다면 메서드를 잘게 나누는 데는 약간의 시간이 걸릴 것이다. 작은 메서드는 실제로 이름을 잘 지었을 때만 그 진가가 드러나므로, 이름을 지을 때 주의해야 한다. 사람들은 때때로 나에게 한 메서드의 길이가 어느 정도 돼야 할지 묻는다. 그러나 중요한 것은 메서드의 이름과 메서드 몸체의 의미적 차이다. 뽑아내는 것이 코드를 더욱 명확하게 하면 새로 만든 메서드의 이름이 원래 코드의 길이보다 길어져도 뽑아낸다. — Martin Fowler
- 객체로 책임을 분배할 때 가장 먼저 할 일은 메서드를 응집도 있는 수준으로 분해하는 것
- 일단 메서드를 분리하고 나면 public 메서드는 상위 수준의 명세를 읽는 것 같은 느낌이 든다. 메서드의 구현이 주석을 모아 놓은 것처럼 보이기까지 한다.
객체를 자율적으로 만들자
- 책임 주도 설계 방법에 익숙하지 않다면 일단 데이터 중심으로 구현한 후 이를 리팩터링하더라도 유사한 결과를 얻을 수 있다.
- 처음부터 책임 주도 설계 방법을 따르는 것보다 동작하는 코드를 작성한 후에 리팩터링 하는 것이 더 훌륭한 결과물을 낳을 수도 있다. 캡슐화, 결합도, 응집도를 이해하고 훌륭한 객체지향 원칙을 적용하기 위해 노력한다면 책임 주도 설계 방법을 단계적으로 따르지 않더라도 유연하고 깔끔한 코드를 얻을 수 있을 것이다.