개발자는 기록이 답이다

오브젝트 3장 - 역할, 책임, 협력(2) 본문

기술 서적/OOP

오브젝트 3장 - 역할, 책임, 협력(2)

slow-walker 2023. 11. 28. 16:56

 

3. 역할

역할과 협력

 

객체는 협력이라는 주어진 문맥안에서 특정한 목적을 갖게 되고, 객체의 목적은 협력 안에서 객체가 맡게 되는 책임의 집합으로 표시된다.

이처럼 객체가 어떤 특정한 협력 안에서 수행하는 책임의 집합 역할이라고 부른다.

영화예매 협력에서 "예매하라"라는 메시지를 처리하기에 적합한 객체로 Screening을 선택했다. 하나의 단계처럼 보이는 이 책임 할당 과정은 실제로 두 개의 독립적인 단계가 합쳐진 것이다. 

 

1. 영화를 예매할 수 있는 적절한 역할이 무엇인가 찾는다

2. 역할을 수행할 객체로 Screening 인스턴스를 선택한다

 

역할에 특별한 이름을 부여하지 않았지만 실제로는 익명의 역할을 찾고 그 역할을 수행할 수 있는 객체를 선택하는 방식으로 설계가 진행된 것이다.

 

Screening과 Movie의 협력 역시 마찬가지다. Screening이 전송하는 "가격을 계산하라"라는 메시지를 수신한 객체는 Movie 인스턴스지만 사실은 역할에 관해 먼저 고민하고 역할을 수행할 객체로 Movie를 선택한 것이다. 이 경우에도 역할에 특별한 이름을 부여하지 않았지만 객체를 수용할 수 있는 위치로서 역할이라는 개념은 여전히 존재한다.

 

역할이라는 개념을 이용해서 설계 과정을 더 번거롭게 만드는것일까 역할이 없어도 객체만으로 충분히 협력을 설계할 수 있는것 아닌가?

 

유연하고 재사용 가능한 협력

 

만일 역할이라는 개념을 고려하지 않고 객체에게 책임을 할당한다고 가정해보자.

Movie가 가격을 계산하기 위해서는 할인 요금이 필요하다. 따라서 "할인 요금을 계산하라"라는 메시지를 전송해서 외부 객체에게 도움을 요청한다. 

 

영화 예매 도메인에서는 금액 할인 정책과 비율 할인 정책 2가지 종류가 존재하기 때문에 해당 객체가 "할인 요금을 계산하라" 메시지에 응답할 수 있어야 한다. 그렇다면 두 종류의 객체가 참여하는 협력을 개별적으로 만들어야 할까?

 

안타깝게도 이런 방식으로 두 협력을 구현하면 대부분의 코드가 중복되고 말것이다. 프로그래밍에서 코드 중복은 모든 문제의 근원이기 때문에 이런 방법은 피해야 한다.

 

문제를 해결하기 위해서는 객체가 아닌 책임에 초점을 맞춰야 한다. 책임의 관점에서 두 협력을 바라보면 AmountDiscountPolicy와 PercentDiscountPolicy 모두 할인 요금 계산이라는 동일한 책임을 수행한다. 따라서 객체라는 존재를 지우고 할인 요금을 계산하라라는 메시지에 응답할 수 있는 대표자를 생각한다면 두 협력을 하나로 통합할 수 있다.

 

이 대표자를 두 종류의 객체를 교대로 바꿔 끼울 수 있는 일종의 슬롯으로 생각할 수 있고, 이것이 바로 역할이다.

 

여기서 역할이 두 종류의 구체적인 객체를포괄하는 추상화라는 게 중요하다.

AmountDiscountPolicy와 PercentDiscountPolicy 를 포괄할 수 있는 추상적인 이름으로 DiscountPolicy 가 되는 것이다.

 

💡 역할의 구현

추상화라는 말에서 예상했겠지만, 역할을 구현하는 가장 일반적인 방법은 추상 클래스와 인터페이스를 사용하는 것이다.
협력의 관점에서 추상 클래스와 인터페이스는 구체 클래스들이 따라야 하는 책임의 집합을 서술한 것이다.

추상 클래스 -> 책임의 일부를 구현해 놓은 것
인터페이스 -> 일체의 구현없이 책임의 집합만 나열

추상 클래스와 인터페이스는 동일한 책임을 수행하는 다양한 종류의 클래스들을 협력에 참여시킬 수 있는 확장 포인트를 제공한다. 이들은 동일한 책임을 수행할 수 있는 객체들을 협력안에 수용할 수 있는 역할이다.

중요한 것은 역할이 다양한 종류의 객체를 수용할 수 있는 일종의 슬롯이자 구체적인 객체들의 타입을 캡슐화하는 추상화라는 것이다. 일단 협력 안에서 역할이 어떤 책임을 수행해야 하는지를 결정하는 것이 중요하다. 역할을 구현하는 방법은 그 다음 문제다.

객체에게 중요한 것은 행동이며, 역할은 객체를 추상화해서 객체 자체가 아닌 협력에 초점을 맞출 수 있게 한다.

NoneDiscountPolicy 역시 DiscountPolicy역할을 수행하는 객체의 한 종류다.

 

물론 이 과정에는 인터페이스 업캐스팅, 다형성, 늦은 바인딩, 상속, 컴파일 시간 의존성과 실행 시간 의존성의 차이와 같은 다양한 기술적 매커니즘이 숨겨져 있지만, 이런 것들이 모여서 유연하고 재사용 가능한 협력을 만들 수 있다는 기반을 제공한다는 것이 중요하다.

 

객체 대 역할

 

역할이 슬롯이라고 했는데, 오직한 종류의 객체만 협력에참여하는 상황에서 역할이라는 개념을 고려하는 것이 유용할까?

 

협력에 참여하는 후보가 여러 종류의 객체에 의해 수행될 필요가 있다면 그 후보는 역할이 되지만, 단지 한 종류의 객체만이 협력에 참여할 필요가 있다면 후보는 객체가 된다.

 

협력을 구성하기 위해 역할에 적합한 객체가 선택되며, 객체는 클래스를 이용해 구현되고 생성된다.

 

명확한 기준을 세우기 어렵고 정보가 부족한 설꼐 초반에는 어떤 것이 역할이고 객체인지 결정하기가 어려울 것이다.

 

설계초반에는 적절한 책임과 협력의 큰 그림을 탐색하는 것을 가장 중요한 목표로 삼고, 역할과 객체를 명확하게 구분하는 것은 그렇게 중요하지 않다. 따라서 애매하다면 객체로 시작하고 반복적으로 책임과 협력을 정제해가면서 필요한 순간에 객체로부터 역할을 분리해 내는 것이 가장 좋은 방법이다.

 

다양한 객체들이 협력에 참여한다는 것이 확실하다면 역할로 시작하라.

하지만 정확한 결정을 내릭 어려운 상황이라면 구체적인 객체로 시작하라.

 

역할과 추상화

 

2장에서 추상화를 이용한 설계는 추상화 계층만을 이용하면 중요한 정책을 상위 수준에서 단순화 할 수 있으며, 설계가 좀더 유연해진다고 설명했다.

 

 

역할은 공통의 책임을 바탕으로 객체의 종류를 숨기기 때문에 이런 관점에서 역할을 객체의 추상화로 볼 수 있다. 따라서 추상화가 가지는 장점들을 협력의 관점에서 역할에도 동일하게 적용된다.

 

코드 확인할 수 있는 Github 링크

 

GitHub - codesejin/OOP-Object: [오브젝트] - 코드로 이해하는 객체지향 설계

[오브젝트] - 코드로 이해하는 객체지향 설계. Contribute to codesejin/OOP-Object development by creating an account on GitHub.

github.com