객체지향 패러다임의 핵심은 역할, 책임, 협력이다. 클래스, 상속, 지연 바인딩은 구현 매커니즘일 뿐이다.
객체지향의 본질은 협력하는 객체들의 공동체를 창조하는 것이다.
1. 협력
객체지향 원칙을 따르는 애플리케이션의 제어 흐름은 다양한 객체들 사이에 균형 있게 분배된다.
다양한 객체들이 영화 예매라는 기능을 구현하기 위해 메시지를 주고받으면서 상호작용한다.
- 협력: 객체들이 기능을 구현하기 위해 수행하는 상호작용
- 책임: 객체가 협력에 참여하기 위해 수행하는 로직
- 역할: 객체가 협력 안에서 수행하는 책임들
메시지 전송은 협력을 위해 사용할 수 있는 유일한 커뮤니케이션 수단이다.
메시지를 수신한 객체는 메서드를 실행해 요청에 응답한다. 객체는 메시지를 처리할 방법을 스스로 선택한다.
Screening은 Movie에 calculateMovieFee 메시지를 전송함으로써 요금 계산을 요청한다.
요금을 계산하는 데 필요한 기본 요금과 할인 정책을 잘 알고 있는 객체는 Movie다.
Screening은 Movie의 내부 구현을 알지 못한다. Movie가 직접 요금을 계산하기에 Movie는 자율적인 존재가 될 수 있다.
객체를 자율적으로 만드는 가장 기본적인 방법은 내부 구현을 캡슐화하는 것이다.
Screening과 Movie 사이의 결합도는 느슨하며, Movie에 대한 변경의 여파가 Screening 쪽으로 확산되는 것을 막을 수 있다.
협력이 설계를 위한 문맥을 결정한다
객체가 필요하다면 그 이유는 그 객체가 어떤 협력에 참여하고 있기 때문이고,
객체가 협력에 참여할 수 있는 이유는 협력에 필요한 적절한 행동을 보유하고 있기 때문이다.
즉, 객체의 행동을 결정하는 것은 객체가 참요하고 있는 협력이다.
영화 예매 시스템 안의 Movie에는 영화 상영과 관련된 코드는 포함돼 있지 않다.
Movie는 영화를 예매하기 위한 협력에 참여하고 있고, 그 안에서 요금을 계산하는 책임을 지고 있다.
협력이라는 문맥을 고려하지 않고 객체의 행동을 결정하는 것은 아무런 의미가 없다.
협력이 객체의 행동을 결정하고, 행동이 객체의 상태를 결정한다.
객체의 상태는 행동을 수행하는 데 필요한 정보가 무엇인지로 결정된다.
Movie가 기본 요금과 할인 정책 변수를 상태로 포함하는 이유는 요금 계산이라는 행동을 수행하는 데 필요하기 때문이다.
결과적으로 협력이 객체의 행동과 상태 모두를 결정한다. 협력은 객체 설계에 필요한 일종의 문맥을 제공한다.
2. 책임
협력이 갖춰졌다면 협력에 필요한 행동(책임)을 수행하는 적절한 객체를 찾아야 한다.
책임이 메시지보다 추상적이고 개념적으로도 더 크다.
객체에게 할당한 책임이 외부 인터페이스와 내부 속성을 결정한다.
책임: 객체에 의해 정의되는 응집도 있는 행위의 집합
- 하는 것
- 객체를 생성하거나 계산을 수행하는 등의 스스로 하는 것
- 다른 객체의 행동을 시작시키는 것
- 다른 객체의 활동을 제어하고 조절하는 것
- 아는 것
- 사적인 정보에 관해 아는 것
- 관련된 객체에 관해 아는 것
- 자신이 유도하거나 계산할 수 있는 것에 관해 아는 것
Screening
- 하는 것: 영화 예매
- 아는 것: 상영할 영화
Movie
- 하는 것: 예매 가격 계산
- 아는 것: 가격과 할인 정책
아는 것과 하는 것은 밀접하게 연관돼 있다.
적절한 협력이 적절한 책임을 제공하고, 적절한 책임을 객체에게 할당해야 단순하고 유연할 설계가 가능하다.
객체지향 설계에서 가장 중요한 것은 책임이다.
책임 할당
information expert 패턴: 책임을 수행하는 데 필요한 정보를 가장 잘 알고 있는 정보 전문가에게 책임을 할당
1. 영화 예매 시스템이 제공할 책임: 영화 예매
2. 예매를 하기 위한 메시지로 협력 시작
3. 메시지를 처리할 객체 선택: 상영 시간과 기본 요금을 가장 잘 아는 전문가는 Screening
4. 가격 계산을 위한 새로운 메시지 필요
5. 메시지를 처리할 객체 선택: 가격과 할인 정책을 가장 잘 아는 전문가는 Movie
객체지향 설계는 협력에 필요한 메시지를 찾고 메시지에 적절한 객체를 선택하는 반복적인 과정을 통해 이뤄진다.
이렇게 결정된 메시지가 객체의 퍼블릭 인터페이스를 구성한다.
협력 설계의 결과물은 시스템을 구성하는 객체들의 인터페이스와 오퍼레이션의 목록이다.
어떤 경우에는 응집도와 결합도의 관점에서 정보 전문가가 아닌 다른 객체에게 책임을 할당하는 게 더 적절할 수도 있다.
책임 주도 설계
책임을 찾고 책임을 수행할 적절한 객체를 찾아 책임을 할당하는 방식으로 협력을 설계하는 방법
- 시스템이 사용자에게 제공해야 하는 기능인 시스템 책임을 파악
- 시스템 책임을 더 작은 책임으로 분할
- 분할된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아 책임을 할당
- 객체가 책임을 수행하는 도중 다른 객체의 도움이 필요한 경우 이를 책임질 적절한 객체 또는 역할 탐색
- 해당 객체 또는 역할에게 책임을 할당 (두 객체가 협력)
구현이 아닌 책임에 집중해야 한다.
책임을 할당할 때 고려해야 하는 요소
- 메시지가 객체를 결정한다
- 행동이 상태를 결정한다
메시지가 객체를 결정한다
책임을 할당하는 데 필요한 메시지를 먼저 식별하고 메시지를 처리할 객체를 나중에 선택한다.
1. 객체가 최소한의 인터페이스를 가질 수 있게 된다.
필요한 메시지가 식별될 때까지는 객체의 퍼블릭 인터페이스에 어떤 것도 추가되지 않는다.
2. 객체가 충분히 추상적인 인터페이스를 가질 수 있게 된다.
객체의 인터페이스는 어떻게는 how는 숨기고 what을 드러내야 한다.
메시지를 먼저 식별하면 what에 초점을 맞추는 인터페이스를 얻을 수 있다.
행동이 상태를 결정한다
객체의 행동은 객체가 협력에 참여할 수 있는 유일한 방법이다.
객체가 협력에 적합한지를 결정하는 것은 상태가 아닌 행동이다.
상태를 먼저 결정하면 내부 구현이 인터페이스에 노출되기 쉬워 캡슐화를 저해한다.
데이터-주도 설계: 객체의 내부 구현에 초점을 맞춘 설계 방법
개별 객체의 상태와 행동이 아닌 시스템의 기능을 구현하기 위한 협력에 초점을 맞춰야 응집도가 높고 결합도가 낮은 객체들을 만들 수 있다.
상태는 행동을 위한 재료일뿐이다.
3. 역할
역할: 객체가 어떤 특정한 협력 안에서 수행하는 책임의 집합
특정 역할에게 책임을 할당한다고 생각하는 게 좋다.
영화 예매 협력에서 '예매하라'라는 메시지를 처리하기에 적합한 객체로 Screening을 선택
=> 1. 영화를 예매할 수 있는 적절한 역힐이 무엇인지 탐색
2. 역할을 수행할 객체로 Screening 인스턴스 선택
유연하고 재사용 가능한 협력
역할을 통해 유연하고 재사용 가능한 협력을 얻을 수 있다.
역할을 고려하지 않고 책임을 할당해보자.
금액 할인 정책과 비율 할인 정책에 모두 대응하기 위해,
AmountDiscountPolicy와 PercentDiscountPlicy 인스턴스의 두 객체가 할인 요금 계산 메시지에 응답할 수 있어야 한다.
그렇다고 두 협력을 개별적으로 만들면 중복이 발생한다.
따라서 객체가 아닌 책임에 초점을 맞춰야 한다.
두 인스턴스 모두 할인 요금 계산이라는 동일한 책임을 수행한다.
객체가 아닌, 메시지에 응답하는 책임을 생각한다면 두 협력을 하나로 통합할 수 있다.
이 역할은 두 종류의 구체적인 객체를 포괄하는 추상화다. (DiscountPolicy)
역할을 구현하는 가장 일반적인 방법은 추상 클래스와 인터페이스를 사용하는 것이다.
- 추상 클래스: 책임의 일부를 구현해 놓은 것
역할을 수행하는 객체들이 공유하는 상태와 행동의 기본 구현 존재 => DiscountPolicy 추상 클래스 - 인터페이스: 구현 없이 책임의 집합만을 나열해 놓은 것
공통의 구현이 필요없고 책임의 목록만 정의 => DiscountCoundition 인터페이스
역할: 다양한 종류의 객체를 수용하는 슬롯이자, 구체적인 객체들의 타입을 캡슐화하는 추상화
객체 대 역할
협력에 적합한 책임을 수행하는 대상이 한 종류라면 객체로 간주하고,
여러 종류의 객체들이 참여할 수 있다면 역할로 간주하면 된다.
도메민 모델 안에는 개념과 객체와 역할이 뒤섞여 있다.
객체로 시작하고 반복적으로 책임과 협력을 정제해가면서 역할을 분리해내면 된다.
동일한 책임을 서로 다른 방식으로 수행할 수 있는 객체들이 필요해졌을 때 역할을 도입하면 된다.
역할 모델링: 역할을 설계의 중심 개념으로 봄
협력을 구체적인 객체가 아닌 추상적인 역할의 관점에서 설계하면 협력이 유연하고 재사용 가능해진다.
역할의 가장 큰 장점은 설계의 구성 요소를 추상화할 수 있다는 것이다.
역할과 추상화
추상화의 장점
- 추상화 계층만을 이용하면 중요한 정책을 상위 수준에서 단순화할 수 있다.
- 설계가 좀 더 유연해진다.
역할은 객체의 추상화이며, 협력을 추상화한다.
협력이라는 관점에서는 세부적인 사항을 무시하고 추상화에 집중하는 것이 유용하다.
할인 정책과 할인 조건의 종류와 같은 세부 사항을 무시하면 상황을 추상화할 수 있다.
구체적인 객체로 대체 가능한 DiscountPolicy와 DiscountCondition이 바로 역할이다.
앞으로 추가될 미지의 할인 정책과 할인 조건을 수용할 수 있는 유연한 설계를 얻을 수 있다.
객체에게 중요한 것은 행동이며, 동일한 협력을 수행하는 객체들을 추상화할 수 있기 때문에 역할이 중요하다.
역할은 다양한 환경에서 다양한 객체들을 수용할 수 있게 해주므로 협력을 유연하게 만든다.
프레임워크나 디자인 패턴과 같이 재사용 가능한 코드나 설계 아이디어를 구성하는 핵심적인 요소가 역할이다.
배우와 배역
배우는 객체, 배역은 역할로 볼 수 있다.
서로 다른 배우들이 동일한 배역을 연기할 수 있고, 하나의 배우가 다양한 연극에서 서로 다른 배역을 연기할 수 있다.
배우는 연극이 끝나면 배역을 잊는다. 객체도 협력이 끝나면 역할을 잊고 원래의 객체로 돌아올 수 있다.
협력이라는 문맥 안에서 역할은 협력에 참여해서 책임을 수행하는 객체의 일부다.
역할은 객체가 협력에 참여하는 장시 동안에만 존재하는 일시적인 개념이다.
역할은 객체의 페르소나다.
역할은 특정한 객체의 종류를 캡슐화하기 때문에, 동일한 역할을 수행하는 대체 가능한 객체들은 다형적이다.