3장 역할, 책임, 협력
Last updated
Last updated
위 다이어그램 에서는 다양한 객체들이 영화 예매의 기능을 구현하기 위해 메시지를 주고 받으면서 상호작용 하고 있다.
객체들이 애플리케이션의 기능을 구현하기 위해 수행하는 상호작용을 협력이라고 부른다.
객체가 협력에 참여하기 위해 수행하는 로직은 책임이라고 부른다.
객체들이 협력 안에서 수행하는 책임들이 모여 객체가 수행하는 역할이 된다.
협력은 객체지향의 세계에서 기능을 구현할 수 있는 유일한 방법이다. 즉, 두 객체가 상호작용을 통해 더 큰 책임을 수행하게 된다.
협력을 위한 두 객체 사이의 메시지 전송(message sending)은 객체 사이의 협력을 위해 사용할 수 있는 유일한 커뮤니케이션 수단이다.
이런 메시지를 수신한 객체는 메서드를 실행하여 응답하게 된다.
외부의 객체는 오직 메시지 만 전송하며, 메시지를 어떠한 방식으로 처리할지는 수신한 객체가 직접 결정하게 된다.
위 다이어그램에서 예메 요금 계산을 위해 Screening은 Movie에게 calculateMovieFee() 메시지를 전송함으로써 1인 요금을 얻는다
만약 메시지를 사용하지 않고, 요금을 계산하는 작업을 Screening이 수행하게 된다면 Movie의 인스턴스 변수에 직접 접근해야만 한다.
=> Screening은 Movie의 내부 구현에 결합되어 버린다. 가장 큰 문제는 Movie의 자율성이 회손된다.
결과적으로 객체를 자율적으로 만들려면 내부 구현을 캡슐화 해야한다.
캡슐화를 하면 변경에 대한 파급효과를 제한할수 있기 때문에 자율적인 객체는 변경하기도 쉬워진다.
Screening이 요금을 계산하기 위해 Movie의 내부에 직접적으로 접근하는 것은 캡슐화의 원칙을 위반하는 것 이다.
반면 calculateMovieFee와 같이 메시지를 사용하게 되면, Screening과 Movie 사이의 결합도를 느슨하게 유지할수 있다.
어떤 객체가 필요하다면 그 이유는 그 객체가 어떤 협력에 참여하고 있기 때문이다. 그리고 객체가 협력에 참여할 수 있는 이유는 협력에 필요한 적절한 행동을 보유하고 있기 때문이다.
예를 들어 우리의 Movie 객체는 어떤 행동을 수행해야 할까?
우리의 Movie에 포함된 대부분의 메서드는 요금을 계산하는 행동과 관련되어 있다. 이것은 Movie가 영화를 예매하기 위한 협력에 참여하고 있고 그 안에서 요금을 계산하는 책임을 지니고 있기 때문이다.
Movie의 행동을 결정하는 것은 영화 예매를 위한 협력이다. 협력이라는 문맥을 고려하지 않고 Movie의 행동을 결정하는 것은 아무런 의미가 없다. 협력이 존재하기 때문에 객체가 존재한다.
객체의 행동은 협력이 결정하고, 상태는 행동이 경정한다.(협력 -> 행동 -> 상태)
상태는 객체가 행동하는 데 필요한 정보에 의해서 결정된다고 할수있다.
행동은 협력 안에서 객체가 처리할 메시지로 결졍된다.
결과적으로 객체가 참여하는 협력이 객체의 행동과 상태를 모두 결정한다. 협력이 객체 설계의 문맥(context)를 제공하는 것 이다.
협력에 참여하기 위해 객체가 수행하는 행동을 책임이라 부른다.
책임이란 객체에 의해 정의되는 응집도 있는 행위의 집합으로, 객체가 유지해야 하는 정보와 수행할 수 있는 행동에 대해 개략적으로 서술한 문장이다.
객체에게 얼마나 적절한 책임을 할당하느냐? 가 설계의 품질을 결정한다. 객체의 구현 방법은 책임을 결정한 후 에 고민해도 늦지 않다.
크레이그 리만(Craig Larman)은 객체의 책임을 '무엇을 알고 있는가' 와 '무엇을 할 수 있는가' 로 나누었다.
하는 것
객체를 생성하거나 계산을 수행하는 등의 스스로 하는 것
다른 객체의 행동을 시작시키는 것
다른 객체의 활동을 제저하고 조절하는 것
아는 것
사적인 정보에 관해 아는 것
관련된 객체에 관해 아는 것
자신이 유도하거나 계산할 수 있는 것에 관해 아는 것
ex) Screening의 책임은 무엇인가? 영화를 예매하는 것 이다. 또한 자신이 상영할 영화를 알고 있어야 한다.
영화를 예매한다 - 하는것
상영할 영화를 안다 - 아는것
협력 안에서 객체에게 할당한 책임이 외부의 인터페이스와 내부의 속성을 결정하게 된다.
객체는 자신이 맡은 책임을 수행하는 데 필요한 정보를 알고 있을 책임이 있다. (만약 자기가 할수 없다면 이를 도와줄 객체를 알아야 한다)
=> 어떤 책임을 수행하기 위해서는 그 책임을 수행하는 데 필요한 정보도 함께 알아야 할 책임이 있는 것 이다.
사실 협력이 중요한 이유도, 객체에게 할당할 책임을 결정할 수 있는 문맥을 제공하기 때문이다.
책임을 할당한다는 것은 메세지의 이름을 결정하고, 해당 메세지에 응답할 객체를 결정하는 것이다
필요한 정보를 가장 잘 알고있는 전문가에게 그 책임을 할당해야 한다 => INFORMATION EXPERT(정보 전문가) 패턴
객체에게 책임을 할당하기 위해서는 가장 먼저 협력이라는 문맥을 정의해야 한다.
우리의 영화 예매시스템을 예로 들어보자.
시스템이 사용자에게 제공해야 하는 기능은 영화를 예매하는 기능이다. 이 기능을 시스템이 제공할 책임으로 할당하는 것 이다.
객체가 책임을 수행하는 방법 => 메시지 전송
책임을 할당 한다 => 메시지의 이름을 결정하는 것과 같다.
우리의 예시 에서는 "예매하라" 라는 이름의 메시지로 협력(문맥)을 시작해 보자.
메시지를 선택했으면 메시지를 처리할 적절한 객체를 선택해야 한다. 정보 전문가 에게 책임을 할당하면 된다.
영화 예매와 관련된 정보를 가장 많이 알고있는 객체에게 책임을 할당하는 것 이 바람직하다.
영화를 예매하기 위해서는 상영 시간과 기본 요금을 알아야 한다.
누가 해당 정보들을 알고있는 정보 전문가 인가? => Screening 이다.
예매를 위해서는 예매 가격을 계산해야 한다. 하지만 Screening은 가격을 계산하는데 필요한 정보를 충분히 알고 있지 않다.
Screening은 예매의 전문가 이지, 가격계산의 전문가는 아니다. => Screening 외부의 객체에게 가격 계산을 요청해야 한다.
새로운 메시지가 필요하다.
마지막으로 이 "가격을 계산하라" 라는 메시지를 처리할 적절한 객체가 필요하다.
마찬가지로 가격을 계산하는 데 필요한 정보를 가장 많이 알고있는 정보 전문가를 선택해야 한다. => Movie 이다.
가격을 계산하기 위해서는 다시 할인요금이 필요하다. 하지만 이는 Movie의 정보 전문 분야가 아니다.
따라서 Movie는 다시 요금을 계산하는데 필요한 요청을 외부에 전송해야 한다. "할인 요금을 계산하라" 라는 메시지가 필요하다.
이처럼 객체지향 설계는 협력에 필요한 메시지를 찾고, 메시지에 적합한 객체를 선택하는 반복적인 과정이다.
물론 모든 책임 할당 과정이 이렇게 단순하지는 않지만, 기본적으로 책임을 수행할 정보 전문가를 찾는 것이다.
지금까지의 내용의 핵심은 협력을 설계하기 위해서는 책임에 초점을 맞춰야 한다는 것이다.
이처럼 책임을 찾고 책임을 수행할 적절한 객체를 찾아 책임을 할당하는 방식으로 설계하는 방법을 RDD(책임 주도 설계)라 부른다.
1. 메시지가 객체를 결정한다.
메세지를 먼저 식별하고 메시지를 처리할 객체를 나중에 선택했다.
최소한의 인터페이스
충분히 추상적인 인터페이스 : 인터페이스는 무엇을 하는지 표현해야 하지만, 어떻게 수행하는지는 노출해서는 안된다.
2. 행동이 상태를 결정한다
객체가 존재하는 이유는 협력에 참여하기 위해서 이다. 따라서 객체를 객체답게 만드는 것은 상태(데이터)가 아니라 다른 객체에게 제공하는 행동이다.\
캡슐화를 위반하지 않기 위해 구현을 뒤로 미루면서 객체의 행위를 고려하기 위해서는 항상 협력이라는 문맥 안에서 객체를 생각해야 한다.
개별 객체의 상태와 행동이 아닌, 시스템의 기능을 구현하기 위한 협력에 초점을 맞춰야만 응집도가 높고, 결합도가 낮은 객체를 만들수있다.
상태는 단지 정상적인 행동을 수행하기 위한 재료일 뿐이다.
객체가 어떤 특정한 협력 안에서 수행하는 책임의 집합을 역할이라고 부른다.
위 영화 예메 협력과정을 생각해 보자.
1) 영화를 예매할 수 있는 적절한 역할이 무엇인가를 찾는다.
2) 역할을 수행할 객체로 Screening 인스턴스를 선택한다.
익명의 역할을 찾은 후, 그 역할을 수행할 수 있는 객체를 선택하는 방식이다.
역할이 중요한 이유는 역할을 통해 유연하고 재사용 가능한 협력을 얻을 수 있기 때문이다.
만일 익명의 역할이 없다고 해보자.
Movie 가 가격을 계산하기 위해 "할인 요금을 계산해라" 라는 메시지를 보내면, AmountDiscountPolicy 와 PercentDiscountPolicy 인스턴스 2가지가 메시지에 응답할수 있다.
그렇다면 두 종류의 객체가 참여하는 협력을 다음과 같이 개별적으로 만들어야 할까?
아니다! 순수하게 책임의 관점에서 두 AmountDiscountPolicy 와 PercentDiscountPolicy 를 보면 둘다 할인 요금 계산 이라는 동일한 책임을 수행한다는 사실을 알 수 있다.
동일한 책임을 수행할 대표자가 바로 역할 이다.
역할이 두 종류의 구체적인 객체를 포괄하는 추상화 이다.
AmountDiscountPolicy 와 PercentDiscountPolicy 를 포괄하는 추상적인 이름으로 DiscountPolicy를 정할수 있다.
DiscountPolicy 역할을 수행할 수 있는 어떤 객체라도 이 협력에 참여할 수 있게 된다.
만약 협력에 적합한 책임을 수행할 대상이 한 종류라면 객체로 간주한다.
만약 여러 종류의 객체들이 참여할 수 있다면 역할로 간주한다.