객체들이 애플리케이션의 기능을 구현하기 위해 수행하는 상호작용을 협력이라 한다.
객체가 협력에 참여하기 위해 수행하는 로직은 책임이라 한다.
객체들이 협력 안에서 수행하는 책임들이 모여 객체가 수행하는 역할을 구성한다.
협력이란 어떤 객체가 다른 객체에 무엇인가를 요청하는 것이다.
한 객체는 어떤 것이 필요할 때 다른 객체에 전적으로 위임하거나 서로 협력한다.
두 객체가 상호작용을 통해 더 큰 책임을 수행하는 것이다.
이때 객체 사이의 협력을 위해 사용할 수 있는 유일한 커뮤니케이션 수단은 메시지이다.
다른 객체가 가지고 있는 메서드를 선택함으로써 메시지를 보낸다고 생각한다.
이렇게 메시지를 수신한 객체는 메서드를 실행해 요청에 응답한다.
객체는 메서드 내에서 스스로 수신한 메시지를 처리한다.
이것이 객체가 자기 일을 스스로 처리할 수 있는 자율적인 존재인 이유다.
그리고 자율적인 객체는 자신의 상태를 직접 관리하고 스스로의 결정에 따라 행동한다.
객체의 자율성을 보장하기 위해서는 필요한 정보와 정보에 기반한 행동을 같은 객체 안에 모아놓아야 한다.
그렇기에 객체는 자신이 할 수 없는 일을 다른 객체에 위임하며 협력에 참여하는 객체들의 전체적인 자율성을 향상할 수 있다.
객체의 행동을 결정하는 것이 협력이라면 객체의 상태를 결정하는 것은 행동이다.
객체의 상태는 그 객체가 행동을 수행하는 데 필요한 정보가 무엇인지로 결정된다.
객체는 자신의 상태를 스스로 결정하고 관리하는 자율적인 존재이기 때문에 객체가 수행하는 행동에 필요한 상태도 함께 가지고 있어야 한다.
상태는 객체가 행동하는데 필요한 정보에 의해 결정되고 행동은 협력안에서 객체가 처리할 메시지로 결정된다.
결과적으로 객체가 참여하는 협력이 객체를 구성하는 행동과 상태 모두를 결정한다.
협력에 참여하기 위해 객체가 수행하는 행동을 책임이라 한다.
객체의 책임은 객체가 '무엇을 알고 있는가'와 '무엇을 할 수 있는가'로 구성된다.
이렇게 '아는 것'과 '하는 것'은 밀접하게 연관돼 있다.
객체는 책임을 수행하기 위해서 그 책임을 수행하는데 필요한 정보도 함께 알아야 할 책임이 있는 것이다.
그 책임은 객체가 자신이 맡은 책임을 수행하는 데 필요한 정보를 알고 있을 책임
그리고 객체가 자신이 할 수 없는 작업을 도와줄 객체를 알고 있을 책임이 있다.
객체가 무엇을 할 수 있는가에 관한 책임은 협력이라는 문맥 안에서 이루어진다.
이 책임은 객체가 협력안에서 메시지를 받으면 메시지를 처리할 메서드를 가지고 있을 책임이 있다는 뜻이다.
자율적인 존재인 객체는 할당받을 책임을 어떻게 수행할지 메서드 내부에서 스스로 결정한다.
객체를 객체답게 만다는 것은 객체의 상태가 아니라 객체가 다른 객체에 제공하는 행동이다.
개별 객체의 상태와 행동이 아닌 시스템의 기능을 구현하기 위한 협력에 초점을 맞춰야만 응집도가 높고 결합도가 낮은 객체들을 창조할 수 있다.
상태는 단지 객체가 행동을 정상적으로 수행하기 위해 필요한 재료일 뿐이다.
객체가 어떤 특정한 협력 안에서 수행하는 책임의 집합을 역할이라 부른다.
실제로 협력을 모델링할 때는 특정한 객체가 아니라 역할에게 책임을 할당하는 게 좋다고 한다.
책에 있는 예제인 AmountDiscountPolicy
와 PercentDiscountPolicy
의 책임을 살펴보자.
객체라는 존재를 지우고 할인 요금을 계산하라
라는 메시지에 응답할 수 있는 대표자로 생각한다면 두 책임을 하나의 역할을 수행한다고 생각할 수 있을 것이다.
다시 한번 정리하면 협력 안에서 수행되는 동일한 책임의 집합을 역할이라 할 수 있는 것이다.
이렇게 역할은 공통의 책임을 바탕으로 객체의 종류를 숨길 수 있기 때문에 역할을 객체의 추상화로 볼 수 있다.
협력이라는 문맥 안에서 역할은 특정한 협력에 참여해서 책임을 수행하는 객체의 일부이다.
일반적으로 역할은 객체가 협력에 참여하는 잠시 동안에만 존재하는 일시적인 개념이다.
역할은 모양이나 구조에 의해 정의될 수 없으며 오직 시스템의 문맥 안에서 무엇을 하는지 의해서만 정의될 수 있다.
역할은 객체의 페르소나인 것이다.
간단한 코드를 통해서 알아보자.
책의 Screening 클래스와 Movie 클래스는 "영화 가격을 계산하라"이라는 협력에 대해 알아보자.
public class Screening {
private Movie movie; // 객체가 책임을 수행하는 데 필요한 정보
public Money calculateFee(int audienceCount) {
retuen movie.calculateMovieFee(this).times(audienceCount); // movie에게 보내는 메시지
}
}
public class Movie {
private Money fee; // 객체가 책임을 수행하는 데 필요한 정보
private DiscountPolicy discountPolicy; // 객차가 자신이 할 수 없는 작업을 도아줄 객체
public Money calculateMovieFee(Screening screening) { // Movie의 책임, 즉 "영화 가격을 계산하라"라는 협력에서 메시지를 받아 처리하는 메서드
Long discountMoney = discountPolicy.caclulateDiscountAmount(screening);
return fee.minu(discountMoney);
}
}
"영화 가격을 계산하라"이라는 협력에서 Screening 클래스는 "Movie 클래스"를 알 책임을 가지고 Movie 클래스는 이 협력안에서 "영화 값을 계산할" 책임을 가진다.
Screening 클래스는 movie.calculateMovieFee(this).times(audienceCount)
로 Movie 클래스에 메시지를 보내고
Movie 클래스는 public Money calculateMovieFee(Screening screening)
라는 메서드를 스스로 정의함으로써 자율성을 가지고 메시지를 처리한다.
역할에 대해서 알아보기 위해 위의 예시를 아래처럼 조금 수정해보자.
public class Screening {
private Media media;
public Money calculateFee(int audienceCount) {
retuen media.calculateMediaFee(this).times(audienceCount);
}
}
public interface Media {
Money calculateMediaFee(Screening);
}
public class Movie implements Media {
private Money fee;
private DiscountPolicy discountPolicy;
public Money calculateMediaFee(Screening screening) { // 책임 -> 역할
Long discountMoney = discountPolicy.caclulateDiscountAmount(screening);
return fee.minu(discountMoney);
}
}
public class Sports implements Media {
private Money fee;
private DiscountPolicy discountPolicy;
public Money calculateMediaFee(Screening screening) { // 책임 -> 역할
Long discountMoney = discountPolicy.caclulateDiscountAmount(screening);
return fee.minu(discountMoney);
}
}
이제는 calculateMediaFee
라는 책임이 Movie
그리고 Sports
동일하게 존재한다.
즉, calculateMediaFee
는 이제 책임에서 역할이 된 것이다.