책임 할당하기
책임 중심 설계를 위해서는 다음과 같은 원칙을 따라야 한다.
- 데이터보다 행동을 먼저 결정하라
- 협력이라는 문맥에서 책임을 결정하라
객체는 데이터보다 어떠한 책임을 가지는지를 먼저 고민해야 한다. 그리고 그 책임을 부여하는데 도움을 받기 위해선 협력 내에서 힌트를 얻을 수 있다. 그리고 이런 객체지향을 위해 대중적으로 널리 알려진 패턴이 GRASP 패턴이다. 이는 General Responsibility Assignment Software Pattern으로 객체에게 책임을 할당할 때 지침으로 삼을 수 있는 원칙들의 집합을 패턴으로 나타낸 것이다.
도메인 개념에서 출발하기
설계를 시작할 때 우선 해야 할 행동은 도메인 개념에서 출발하기 이다. 설계 전에 도메인 내에 존재하는 많은 개념들을 책임을 할당하는 객체의 대상으로 활용하면 형상화하기 쉬워진다. 즉, 도메인 개념과 개념 사이의 관계를 대략적으로 표시하는 것 이다.
책임 할당하기
설계를 시작하면서 처음으로 고려해야할 사항은 어플리케이션이 제공해야 하는 기능을 어플리케이션의 책임으로 생각하는 것이다. 이 책임을 어플리케이션에게 전송된 메세지라 간주하고 이를 책임질 첫 번째 객체를 선택하는 것으로 시작한다. 가령 다음과 같이 책임 할당의 대상을 찾아 볼 수 있다.
- 메세지를 전송할 객체는 무엇을 원하는가?
- 메세지를 수신할 적합한 객체는 누구인가?
이로부터 시작해서 하나씩 타고 들어갈 수 있다. 가령 앞선 예를 들었던 커피 판매 어플리케이션의 경우 주문하라
라는 메세지를 받을 객체는 Order가 된다. 그리고 이에 대해 가격을 계산하라
라는 메세지는 어떤 객체가 책임을 가질 것 인가? 와 같은 순으로 결정할 수 있다.
높은 응집도, 낮은 결합도
그 다음으로 고려되야 할 사항은 결합도와 응집도에 대한 부분이다. 동일한 기능을 구현할 수 있는 설계는 무수히 많이 존재한다. 그 중에서 어떤 설계를 택할지에 대해 고민이 되는 경우도 있다. 그럴때는 응집도와 결합도의 측면에서 고민해 보면 좋다. 이를 LOW COUPLING 패턴과 HIGH COHESION 패턴이라 한다.
Low Couping 패턴은 어떻게 하면 의존성을 낮추고 변화의 영향을 줄이며 재사용성을 증가시킬까에 대한 고민에서부터 출발한다. 이를 위해선 설계의 전체적인 결합도가 낮게 유지되도록 책임을 할당하면 된다. 낮은 결합도는 모든 설계 과정에서 염두해야 하는 원리이며 설계 결정을 평가할 때 적용할 수 있는 평가원리이다. 여러 설계에 대한 대안이 나왔을 때 낮은 결합도를 유지할 수 있는 설계를 선택해야 한다.
High Cohesion 패턴은 어떻게 하면 복잡성을 관리 가능한 수준으로 유지할 것인가에 대한 해답을 제시한다. 높은 응집도를 유지하도록 책임을 할당하는 방식으로 말이다. 낮은 결합도와 마찬가지로 설계 과정에 있어 염두해야 할 원리이다.
위와 같은 사항을 통해 책임 주도 설계에 대해 전반적인 이해를 할 수 있다. 이와 더불어 코드를 통해서 조금 더 고민해 볼 수 있는 부분들이 있다.
변경의 이유에 따라 클래스를 분리하기
클래스는 변경의 이유에 따라 분리되어야 한다. 가령 앞선 커피 주문 시스템에서 Coffee의 클래스를 보면 하나의 클래스 내에 두 개의 할인정책에 따른 코드가 나타난다. 이는 새로운 할인 정책이나, 기존 정책의 변경과 같은 수정이 필요할 때 기존 코드를 수정하게 되는 문제가 생긴다.
이를 파악하기 위한 방법은 인스턴스 변수가 초기화되는 시점을 이용하는 것이다. 가령 AmountDiscount 정책을 위한 Coffee 에서는 PercentDiscount에 대한 변수 초기화가 이루어지지 않는다.
두번째 방법은 메서드들이 인스턴스 변수를 사용하는 방식을 파악하는 것이다. 모든 메서드가 객체의 모든 속성을 사용한다면 이는 응집도가 높다고 말할 수 있다. 반대로 메서드들이 사용하는 속성에 따라 그룹이 나뉜다면 응집도가 약하다고 말할 수 있다. 이를 해결하기 위해서는 클래스를 분리해야 한다.
이에 따라 Coffee 객체에서 이 내용을 분리하기 위해 AmountDiscount 클래스와 PercentDiscount 클래스를 나누었다고 하자. 이렇게 나누기만 하면 또 다른 문제가 생긴다. 두 할인정책을 가지기 위해 Coffee 객체는 두 인스턴스가 존재해야 한다. 이 문제는 다형성의 관점에서 해결할 수 있다. AmountDiscount와 PercentDiscount의 책임은 할인에 대한 결과를 제공하는 것이다. 이 책임을 기준으로 분리를 한다면 Discount라는 인터페이스를 만들고 Coffee 객체는 인터페이스를 통해 커뮤니케이션을 하도록 설계하면 된다.