본문 바로가기
IT 공부/객체지향 설계 공부

책임과 역할에 대한 상세한 이야기 (오브젝트 3장)

by exdus3156 2023. 12. 7.

1. 역할, 책임, 협력

조영호님의 <객체지향의 사실과 오해>에서는 객체지향의 핵심 원칙을 역할, 책임, 협력의 구조로 소개한다. 간단히 말해 소프트웨어를 각자의 '책임'을 수행하며 서로 '협력'하는 '역할'들로 명세를 짜는 것이다. 역할과 책임에 대한 구체적인 실천은 런타임에 '객체(object)'가 수행한다.

이미 위 포스팅 링크에서 많은 내용을 정리했다. 따라서 여기서는 같은 저자의 <오브젝트> 챕터 3장에 나오는 설명 중 새롭게 알게 된 사실만 따로 정리하려 한다.

 

2. 책임 할당: 정보 전문가 패턴(Information Expert)

좋은 객체지향 설계의 핵심은 책임을 적절한 객체에게 할당하는 것이다. 

크레이그 라만(Graig Larman)은 객체의 책임을 크게 하는 것(doing)아는 것(knowing)의 두 가지 범주로 나누어 해석한다. (명령과 쿼리 이야기가 아니다.) 이 두 가지는 서로 밀접하게 관련되어 있다. 변호사가 법률 지식을 알고 있어야 변호 서비스를 할 수 있듯이, 객체 또한 자신이 알고 있는 것을 토대로 어떤 행동을 수행할 수 있다.

책임을 할당하는 기본적인 전략은 식별된 메시지를 수행할 때 필요한 지식을 어떤 객체가 가지고 있는지 파악하는 것이다. 이것을 "정보 전문가 패턴"이라고 부른다.

책에 나온 예제를 보자. 영화 예매 프로그램에서 시스템의 가장 중요한 목표는 예매하는 것이다. 따라서 우리는 이 행동을 "예매하라!" 라는 메시지로 식별할 수 있다.

그렇다면 이 행동을 수행할 객체는 과연 누구일까? 후보군을 몇 가지 생각해볼 수 있다. 예매(Reservation) 객체도 후보가 될 수 있고, 상영(Screening) 객체도 후보가 될 수 있다. 영화(Movie) 객체도 그럴듯하다.

언뜻 생각하면 왠지 예매(Reservation) 객체가 가장 그럴듯하다. 그러나 예매를 위해서는 영화, 가격, 상영 정보, 가격, .. 등의 정보를 알고 있어야 한다. 그것과 가장 많이 관련된 객체는 예매(Reservation) 객체가 아니라 상영(Screening) 객체다! 따라서 예매하기(reserve) 책임 할당 대상의 최종 후보는 상영(Screening) 객체가 된다.

여기서 얻을 수 있는 교훈이 몇 가지 있다.

먼저, 책임을 할당할 때는 객체의 타입(클래스 이름)을 가지고 책임 할당 대상을 감각적으로 추론하지 말라는 것이다. 정석은 행동 수행에 필요한 정보를 가지고 있는 객체다. 보통 도메인 구조에서 필요한 것과 가장 많이 관련지어진 객체 타입이 선택된다. 

또한, 가장 먼저 상태가 아니라 행동을 식별해야 한다는 점이다. 우리는 "예매하라!"라는 메시지를 가장 먼저 식별한 뒤에 예매하기 행동에 요구되는 정보들(상영 시간, 요금, 영화, 가격, ...)을 떠올렸다. 이처럼 상태에서 시작하지 않고 행동에서 상태를 역산했다.

만약 행동이 아니라 객체의 타입(클래스 이름)을 토대로 필요한 정보를 구축하는 것부터 시작하면, 예매하기 행동에 필요한 정보들이 이런 저런 객체들로 산발되어 존재할 것이다.

 

2. 역할에 대한 실천적인 의의

역할(Role)은 책임이나 협력보다 훨씬 더 추상적인 의미라 객체지향의 실천적인 관점에서 그 의미와 의의를 파악해내기가 쉽지 않은 개념이다.

이렇듯 역할 개념이 확실하게 와닿지 않는 이유는 우리가 책임을 식별하고 그 책임을 부여할 객체를 찾을 때 이미 존재하는 객체 타입(※일반적으로 클래스 이름으로 표현됨)을 후보군을 찾기 때문이다.

그러나 이러한 과정은 사실 역할을 따지는 중간 단계가 생략된 것이다. 정확하게 과정을 되짚어보면, 먼저 책임을 식별한 뒤에 해당 책임을 맡을 수 있는 역할이 무엇인지 고민한다. 그 다음 객체 후보군 중에서 해당 역할을 맡을 수 있는 객체를 찾아 책임을 할당한다.

예를 들어, "예매하라!"라는 책임을 식별한 뒤에 우리는 머릿속으로 '예매하는 역할'에 대한 이미지를 어렴풋하게 떠올린다. 그리고 객체 후보군 중 상영(Screening) 객체가 이 역할을 수행하기 적절하다고 판단했다. 

역할은 책임을 할당할 때 머릿속에서 무의식적으로 활용되기 때문에 그 실체를 파악하기가 쉽지 않다. 대부분은 책임을 떠올리는 순간 역할이 아니라 바로 객체를 찾을 것이다. 심지어 객체는 보통 추상화/일반화를 통해 분류 가능한 개념(타입)으로 미리 정리되기 마련이므로 객체가 아니라 클래스에게 바로 책임을 부여할지도 모른다. 그렇다면 왜 굳이 번거롭게 역할을 고민해야 하는 것일까?

역할은 협력의 추상화다. 우리가 객체가 아니라 역할을 고민해야 하는 이유는 역할이 구체적인 객체보다 협력 관계 속에서 더 추상적이고 일반화된 협력을 고민하게끔 도와주기 때문이다.

예를 들어, 영화(Movie) 객체는 할인 정책(Discount Policy) 객체에게 할인 요금 계산을 요청한다. 이때 비즈니스 규칙에 의해 할인 정책으로 정액 할인(Amount Discount)비율 할인(Percent Discount)이 있다고 하자. 만약 객체에게 책임을 할당해야 한다면 "정액 할인 요금 계산"과 "비율 할인 요금 계산"을 따로 설계하고 메시지를 디자인해야 할 것이다.

하지만 정액 할인이나 비율 할인이나 모두 할인 요금을 수행하는 것이다. 따라서 개별적인 객체들은 사실 "할인 요금을 계산하는 역할"을 동등하게 수행한다.

이때 비로소 역할이라는 추상적인 개념이 설계 상에서 명확한 존재로 드러난다. 영화(Movie)는 할인 요금을 계산하기 위해 "할인 요금을 계산하는 역할"에게 "요금 계산 요청" 메시지를 전송한다. 이 역할의 이름을 간단히 DiscountPolicy라고 부르자. 이 역할은 추상클래스나 인터페이스로 구현될 수 있다. 그리고 이것을 상속해 정액 할인과 비율 할인 객체가 역할을 수행하는 것이다.

 

3. 역할 vs 객체

그럼에도 사실 책임을 할당하고 나면 결과적으로 특정 역할이 식별되는 경우보다는 구체적인 객체에게 책임이 할당되는 경우가 많다.

레베카 워프스브룩은 이를 다음과 같이 구분했다.

하나의 객체가 하나의 역할을 수행한다면 그 둘은 같다. 그런데 어떤 협력에서는 둘 이상의 객체가 하나의 책임을 수행할 수도 있다. 이때는 역할과 객체가 명시적으로 구분된다. 즉, 역할이란 서로 다른 방법으로 실행할 수 있는 책임들의 집합을 추상적으로 표현한 것이다.

책임을 할당할 때 결과적으로 하나의 구체적인 객체가 책임을 홀로 담당할 수 있다면, 역할의 후보군이 해당 객체 하나 뿐이므로 사실상 역할과 객체가 같다. 

그러나 어떤 협력과 책임은 적절하게 추상화할 수만 있다면 서로 다른 객체를 한데 묶어서 생각할 수 있다. AmountDiscount(정액할인)이나 PercentDiscount(비율할인)이나 결국 "할인 금액을 계산해준다"는 추상적인 관점에서 보면 같은 책임을 수행한다고 볼 수 있다.

결론적으로 우리가 명심해야 할 것은 협력과 책임을 논할 때는 구체적인 객체가 아니라 역할이라는 추상적인 의미를 떠올리는 것이 좋다는 것이다. 결과적으로 구체적인 객체 하나가 특정 역할 하나를 그대로 수행하는 경우 그 객체가 그대로 역할이 된다. 하지만 책임과 협력의 추상화를 통해 협력 관계를 단순하게 만들어 한 번에 여러 객체를 동일한 역할 하나에 넣을 수도 있다.

협력과 책임은 역할과 관련이 있다. 그리고 해당 역할을 수행할 수 있는 객체를 찾는다. 찾았으면 그 객체를 구현할 클래스를 설계한다.

 

물론 역할과 객체가 동등한 경우가 많기 때문에 실제 설계 실무에서는 이 둘을 억지로 구분하는 것이 벅찰 때도 있다. 따라서 필요한 순간에 역할과 객체를 개념적으로 분리한다면, 보다 더 높은 관점에서 협력의 큰 그림을 재구상할 때 도움이 될 것이다.