책임에 초점을 맞춰 설계할 때 직면하는 가장 큰 문제는 어떤 객체에게 어떤 책임을 할당할지를 결정하기 어렵다는 점이다.
책임 할당 과정은 일종의 트레이트오프 활동이다.
책임 주도 설계를 향해
데이터 중심의 설계에서 책임 중심의 설계로 전환하기 위한 원칙
- 데이터보다 행동을 먼저 결정하라
- 데이터는 객체가 책임을 수행하는 데 필요한 재료를 제공할 뿐이다.
- 객체를 설계하기 위한 질문의 순서를 바꾸자.
- 데이터 중심의 설계 : "이 객체가 포함해야 하는 데이터가 무엇인가" -> "데이터를 처리하는 데 필요한 오퍼레이션은 무엇인가"
- 책임 중심의 설계 : "이 객체가 수행해야 하는 책임은 무엇인가" -> "이 책임을 수행하는 데 필요한 데이터는 무엇인가"
- 협력이라는 문맥 안에서 책임을 결정하라
- 적합한 책임이란 메시지 수신자가 아니라 메시지 전송자에게 적합한 책임을 의미한다.
즉, 메시지를 전송하는 클라이언트의 의도에 적합한 책임을 할당해야 한다.
- 객체를 가지고 있기 때문에 메시지를 보내는 것이 아니다.
메시지를 전송하기 때문에 객체를 갖게 된 것이다.
책임 주도 설계
핵심은 책임을 결정한 후에 책임을 수행할 객체를 결정하는 것
협력에 참여하는 객체들의 책임이 어느 정도 정리될 때까지는 객체의 내부 상태에 대해 관심을 가지지 않는 것
- 책임 주도 설계의 흐름
- 시스템이 사용자에게 제공해야 하는 기능인 시스템 책임을 파악
- 시스템 책임을 더 작은 책임으로 분할
- 분할된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아 책임을 할당
- 다른 객체의 도움이 필요한 경우 이를 책임질 적절한 객체 또는 역할을 찾는다
- 해당 객체 또는 역할에게 책임을 할당함으로써 두 객체가 협력하게 된다.
책임 할당을 위한 GRASP 패턴
GRASP (General Responsibility Assignment Software Pattern) (일반적인 책임 할당을 위한 소프트웨어 패턴)
객체에게 책임을 할당할 때 지침으로 삼을 수 있는 원칙들의 집합을 패턴 형식으로 정리한 것
- 도메인 개념에서 출발하기
- 설계를 시작하기 전에 도메인에 대한 개략적인 모습을 그려 보는 것이 유용하다. -> 출발점으로 삼기 위해
- 정보 전문가에게 책임을 할당하라 - Information Expert
- 객체에게 책임을 할당하는 첫 번째 원칙은 책임을 수행할 정보를 알고 있는 객체에게 책임을 할당하는 것이다.
- 객체가 정보를 '알고' 있다고 해서 그 정보를 '저장'하고 있을 필요는 없다.
-> '정보' 전문가에서 '정보'는 '데이터'와 다르다.
- 스스로 처리할 수 없는 작업이 있다면 외부에 도움을 요청해야 한다
- 높은 응집도와 낮은 결합도 - High Cohesion & Low Coupling
- Low Coupling : 설계의 전체적인 결합도가 낮게 유지되도록 책임을 할당하라.
- High Cohesion : 높은 응집도를 유지할 수 있게 책임을 할당하라
- 책임과 협력의 품질을 검토하는 데 사용할 수 있는 중요한 평가 기준
- 창조자에게 객체 생성 책임을 할당하라 - Creator
- 객체를 생성할 책임을 어떤 객체에게 할당할지에 대한 지침
- 객체 A를 생성해야 할 때 이하 조건을 최대한 많이 만족하는 B에게 객체 생성 책임을 할당하라.
- B가 A 객체를 포함하거나 참조한다.
- B가 A 객체를 기록한다.
- B가 A 객체를 긴밀하게 사용한다.
- B가 A 객체를 초기화하는 데 필요한 데이터를 가지고 있다(이 경우 B는 A에 대한 정보 전문가다)
협력과 책임이 제대로 동작하는지 확인할 수 있는 유일한 방법은 코드를 작성하고 실행해 보는 것뿐이다.
올바르게 설계하고 있는지 궁금한가? 코드를 작성하라.
구현을 통한 검증
- 다형성을 통해 분리하기 - Polymorphism
- 객체의 타입에 따라 변하는 행동이 있다면 타입을 분리하고 변화하는 행동을 각 타입의 책임으로 할당하라 (역할)
- 변경으로부터 보호하기 - Protected Variations
설계를 주도하는 것은 변경이다.
- 코드를 이해하고 수정하기 쉽도록 최대한 단순하게 설계
- 코드를 수정하지 않고도 변경을 수용할 수 있도록 코드를 더 유연하게 만드는 것
대부분의 경우 '1'이 더 좋은 방법이지만
유사한 변경이 반복적으로 발생하고 있다면 복잡성이 상승하더라도 유연성을 추가하는 '2'의 방법이 더 좋다.
책임 주도 설계의 대안
설계가 어려울 때 실행되는 코드를 얻고 난 후에
코드 상에 드러나는 명확한 책임들을 올바른 위치로 이동시키는 방법을 사용한다.
- 리팩터링
- 캡슐화를 향상시키고, 응집도를 높이고, 결합도는 낮추는 작업
- 동작은 바뀌지 말아야 한다.
- 메서드 응집도
- 긴 메서드
- 파악하기 어려움
- 수정 어려움
- 일부 로직 변경 시 버그 발생 위험 높음
- 로직 재사용이 불가능
- 코드 중복
- 짧은 메서드
- 재사용의 가능성이 높아진다
- 일련의 주석을 읽는 것 같은 느낌이 든다
객체를 자율적으로 만들자
메서드를 다른 클래스로 이동시킬 때는 인자에 정의된 클래스 중 하나로 이동하는 경우가 일반적이다.
- 데이터를 사용하는 메서드를 데이터를 가진 클래스로 이동시키면
- 캡슐화, 높은 응집도, 낮은 결합도를 가지는 설계를 얻는다