5. 책임 할당하기
책임 주도 설계를 향해
- 데이터 중심 => 책임 중심
- 데이터보다 행동을 먼저 결정하라
- 협력이라는 문맥 안에서 책임을 결정하라
협력이라는 문맥 안에서 책임을 결정하라
- 책임은 객체의 입장이 아니라 객체가 참여하는 협력에 적합해야 한다.
- 메시지를 전송하는 클라이언트의 의도에 적합한 책임을 할당해야 한다
- 메시지가 객체를 선택하게 해야 한다
- 클래스 기반 설계에서 메시지 기반 설계로 전환해야 한다
- “이 클래스가 무엇을 해야 하지?” => “메시지를 전송해야 하는데 누구에게 전송해야 하지”
- 메시지를 전송하기 때문에 객체를 갖게 된다
- 메시지를 수신하기로 결정된 객체는 메시지를 처리할 책임을 할당 받게 된다
- 메시지를 먼저 결정하기에, 메시지 전송자의 관점에서 수신자가 깔끔하게 캡슐화 된다
책임 할당을 위한 GRASP 패턴
- General Responsibility Assignment Software Pattern: 크레이크 라만이 패턴 형식으로 제안
도메인 개념에서 출발하기
- 도메인 개념들을 책임 할당의 대상으로 사용하면 코드에 도메인의 모습을 투영하기가 좀 더 수월해진다.
- 도메인 개념을 정리하는 데 너무 많은 시간을 들이지 말고 빠르게 설계와 구현을 진행하라
- 올바른 도메인 모델이란 존재하지 않는다
정보 전문가에게 책임을 할당하라
책임을 전송된 메시지로 간주하고, 메시지를 책임질 첫 번째 객체를 선택하는 것으로 설계를 시한다
- Ex) 애플리케이션은 영화를 예매할 책임이 있다.
첫 번째 질문: 메시지를 전송할 객체는 무엇을 원하는가?
- 메시지의 이름으로 “예매하라” 가 적절하다
두 번째 질문: 메시지를 수신할 적합한 객체는 누구인가?
책임을 수행할 정보를 알고 있는 객체에게 책임을 할당=> 정보 전문가(Information Expert) 패턴
가장 기본적인 책임 할당 원칙
책임을 수행하는 데 필요한 정보를 가지고 있는 객체에게 할당하라
정보 전문가는 정보를 알고 있지만, 그 정보를 꼭 데이터로 저장하고 있을 필요는 없다
높은 응집도와 낮은 결합도
- 객체에 책임을 할당할 때 다양한 대안이 존재한다면 높은 응집도와 낮은 결합도 측면에서 생각해라
- Low Coupling, High Cohesion 패턴이라고 한다
- Low Coupling
- 의존성을 낮추고 변화의 영향을 줄이며 재사용성을 증가시킬 수 있는 방법
- 설계의 전체적인 결합도가 낮게 유지되도록 책임을 할당하라
Movie
와DiscountCondition
은 이미 결합돼 있다.Screening
과DiscountCondition
이 협력 할 경우 새로운 결합도가 생성된다.- 따라서
Movie
와DiscountCondition
이 렵력하는 것이 더 나은 설계 대안이다.
- 의존성을 낮추고 변화의 영향을 줄이며 재사용성을 증가시킬 수 있는 방법
- High Cohesion
- 어떻게 복잡성을 관리할 수 있는 수준으로 유지하는 법: 높은 응집도를 유지할 수 있게 책임을 할당해라
Screening
과DiscountCondition
이 협력 할 경우Screening
은 영화 요금 계산과 관련된 책임 일부를 떠안아야 한다.- 예매 요금을 계산하는 방식이 변경될 경우
Screening
도 함께 변경되어야 한다 - 서로 다른 이유로 변경되는 책임을 짊어지게 되므로 응집도 낮아짐
창조자에게 객체 생성 책임을 할당하라
- 영화 예매 협력의 최종 결과물은
Reservation 인스턴스
를 반환하는 것 - Creator(창조자) 패턴은 객체를 생성할 책임을 어떤 객체에게 할당하지에 대한 지침을 제공
- 객체 A가, 아래 조건을 최대한 만족하는 B에게 객체 생성 책임을 할당해라
- B가 A 객체를 포함하거나 참조한다.
- B가 A 객체를 기록한다
- B가 A 객체를 긴밀하게 사용한다
- B가 A 객체를 초기화하는 데 필요한 데이터를 가지고 있다 (B는 A에 대한 전문가다)
- 이미 결합돼 있는 객체에게 생성 책임을 할당하는 것은 설계의 전체적인 결합도에 영향을 미치지 않는다.
- 이미 존재하는 객체 사이의 관계를 이용하기에 낮은 결합도를 유지할 수 있다.
구현을 통한 검증
DiscountCondition 개선하기
DiscountCondition
은 서로 다른 세 가지 이유로 변경될 수 있다.- 새로운 할인 조건 추가
- 순번 조건을 판단하는 로직 변경
- 기간 조건을 판단하는 로직이 변경되는 경우
DiscountCondition
은 하나 이상의 변경 이유를 가지기 때문에 응집도가 낮다- 변경에 이유에 따라 클래스를 분리해야 한다
코드를 통해 변경의 이유를 파악할 수 있는 방법
변경 이유가 하나 이상의 이유로 변경돼야 한다면 응집도가 낮은 것
인스턴스 변수가 초기화되는 시점을 살펴보는 것
- 응집도가 높은 클래스는 인스턴스를 생성할 때 모든 속성을 함께 초기화한다
메서드들이 인스턴스 변수를 사용하는 방식
- 모든 메서드가 객체의 모든 속성을 사용한다면 클래스의 응집도는 높다고 볼 수 있다
타입 분리하기
DiscountCondition
을 순번 조건과 기간 조건이라는 두 개의 독립적인 타입으로 변경- 응집도가 높아지지만, 캡슐화라는 관점에서 설계 품질이 나빠졌음
다형성을 통해 분리하기
- 객체의 암시적인 타입에 따라 행동을 분기해야 한다면 암시적인 타입을 명시적인 클래스로 정의하고 행동을 나눔으로써 응집도 문제를 해결할 수 있다
DiscountCondition
이라는 인터페이스를 구현하는 형식으로 변경- 타입을 분리하고 변화하는 행동을 각 타입의 책임으로 할당해라
- Polymoorphism(다형성) 패턴
- 객체의 타입을 검사해서 타입에 따라 대안들을 수행하는 조건적인 논리를 사용하지말고, 다형성을 이용해 변화를 다루기 쉽게 확장해라
변경으로부터 보호하기
Movie
의 관점에서DiscountCondition
의 타입이 캡슐화된다는 것은 새로운DiscountCondition
타입이 추가되더라도Movie
가 영향을 받지 않는다는 것을 의미- 오직
DiscountCondition
인터페이스를 실체화하는 클래스를 추가하는 것으로 할인 조건의 종류를 확장할 수 있다.
- 오직
- 캡슐화하도록 책임을 할당하는 것을 Protected Variations(변경 보호) 패턴 이라 한다.
- 변경될 가능성이 높으면, 캡슐화 하라
Movie 클래스 개선하기
- 도메인의 구조가 코드의 구조를 이끈다
- 변경 역시 도메인 모델의 일부이다.
- 도메인 모델에는 도메인 안에서 변하는 개념과 이들 사이의 관계가 투영돼 있어야 한다.
변경과 유연성
설게를 주도하는 것은 변경이다; 개발자로서 변경에 대비할 수 있는 방법 두 가지
- 이해하고 수정하기 쉽게 최대한 단순하게 설계
- 코드를 유연하게 만드는 것
상속 대신 합성을 사용하면, 새로운 정책이 추가될 때 마다 유연한 설계가 가능하다
도메인 모델은 단순히 도메인의 개념과 관계를 모아 놓은 것이 아니다.
- 도메인 모델은 구현과 밀접한 관계를 맺어야 한다.
책임 주도 설계의 대안
수정하기 쉬운 소프트웨어로 개선하기 위해 겉으로 보이는 동작은 바꾸지 않은 채 내부 구조를 변경하는 것을 리팩토링 이라 한다.
겉으로 드러나는 동작이 바꿔서는 안된다
캡슐화, 응집도, 결합도 관점에서 개선하고 동작은 그대로 유지해야 한다
메서드 응집도
- 몬스터 메서드(monster method): 코드를 재사용하는 유일한 방법이 원하는 부분을 복사 붙여넣는 것 뿐인, 변경하기 어려운 긴 메서드
- 클래스의 응집도와 마찬가지로 응집도가 높은 메서드는 변경되는 이유가 단 하나이다.
- 작은 메서드들로 조합된 메서드는 마치 주석들을 나열한 것처럼 보이기에 코드를 이해하기 쉽다.
객체를 자율적으로 만들자
- 메서드를 다른 클래스로 이동시킬 때는 인자에 정의된 클래스 중 하나로 이동하는 경우가 일반적이다.