어떤 정보가 필요한 작업을 수행할 때 해당 정보를 가장 잘 아는 정보전문가 클래스에게 위임하고 메세지를 전송하는 방식으로 변경해 캡슐화를 할 수 있다.
예를 들어서 A클래스에 B와 C클래스가 합성돼있으며 B와 C의 상태가 필요한 작업을 수행할 때 getter와 setter를 직접 호출하는 것보다 해당 작업은 각각의 클래스에 메소드화 한 후 만약 다른 클래스가 필요하다면 인자로 인스턴스를 전달한다.
이렇게 하면 불필요한 getter, setter함수를 간소화할 수 있으며 외부에서 추상화단계가 낮은 getter, setter함수를 호출할 이유가 적어진다.
public class Movie {
private Money fee;
private DiscountPolicy discountPolicy;
public Money calculateMovieFee(Screening screening) {
return fee.minus(discountPolicy.calculateDiscountAmount(screening));
} }
Movie는 자기 자신의 요금이 얼마인지 계산할 책임이 있으며 계산을 위해서 fee와 discountPolicy를 상태로 갖는다.
또한 계산 시 상영정보에 있는 시간 등의 정보를 알아야 하므로 Screening 클래스의 인스턴스를 인자로 전달받는다.
할인 요금계산은 영화의 책임과 동떨어져 있으므로 할인요금을 계산하는 메소드가 DiscountPolicy에 위임되어 있는 것은 타당하다.
후보
: 클래스, 컴포넌트, 역할의 후보가 될 키워드
책임
: 하는 것, 아는 것
협력자
: 함께 협력할 후보
메세지를 먼저 만들고 적절한 클래스에 배정하므로써 얻는 장점이 있다.
만약 상태를 먼저 구현하게 된다면 객체의 내부 구현이 공개된 메소드에 노출되어 캡슐화를 저해한다. 또한 공개된 메소드를 변경하게되면 자연스럽게 의존성을 가진 클래스들도 영향을 받는다. 이런 방식을 데이터 주도 설계라고 한다.
반대로 메세지가 객체를 결정하도록 하는 방식을 책임 주도 설계라고 한다.
할인 정책이 여러가지인 경우 할인된 요금을 계산하라는 책임은 동일하게 부여될 수 있다. 이는 중복된 코드를 유발하므로 역할을 하나 추가하여 상속하여 해결할 수 있다.
이 때 사용할 수 있는 방법이 추상클래스 혹은 인터페이스를 활용하는 것이다. 만약 중복된 부분이 있다면 추상클래스에서 템플릿 패턴등으로 해결하고 메세지는 같으나 구현이 다르다면 인터페이스로 해결한다.
OAuth의 경우 카카오, 애플, 구글이 모두 요청하는 주소도 다르고 반환되는 형태도 다르다. 하지만 OAuth2.0이라는 규격을 구현한 상태이기 때문에 해당하는 인터페이스 API가 마련돼있으므로 템플릿 패턴으로 이를 해결할 수 있을 것이다. 이 때 OAuth를 역할, 나머지 요소를 객체로 구현하면 된다.
만약 위의 사례와 달리 확장될 가능성이 없거나 아직 미정인 상태라면 구체적인 객체를 구현하고 다른 클래스를 단순화하고 합쳐나가다보면 역할이 보일 것이다.
객체들의 협력패턴을 추상화하여 역할을 얻어냄으로써 재사용 가능한 시스템을 얻을 수 있다.
객체는 역할의 구현체이며 언제든 해당 역할인 다른 객체들과 교체될 수 있는 유연한 것이다.
다형적으로 설계가 가능하므로 추상화가 잘 된 역할은 중복된 코드도 적고 유연하게 다른 역할들과 협력할 수 있다.