오브젝트 3장. 역할, 책임, 협력

[책 스터디][오브젝트] 3장. 역할, 책임, 협력


본 글을 오브젝트 책을 읽고, 그 내용을 정리한 글입니다.
본 글에 언급되는 실습 코드는 실습 링크를 참고해 주시기 바랍니다.

2장에서 객체지향 프로그래밍을 구성하는 다양한 요소 및 구현 기법에 대해 알아봤다.
2장에서 살펴본 내용들👇

  • 클래스, 추상 클래스, 인터페이스를 조합해 객체지향 프로그래밍을 구조화하는 기본적인 방법
  • 상속을 활용해 다형성을 구현하는 기법
  • 다형성 = 지연 바인딩
  • 상속은 코드를 재사용하는 방법 중 하나이지만, 캡슐화 측면에서 합성 (Composition)이 더 좋다.
  • 유연한 객체지향 프로그래밍을 위해서 컴파일 의존성과 런타임 의존성이 달라야 한다.


이것들이 과연 가장 중요한 내용일까?
아니다. 객체지향 패러다임의 핵심은 객체의 역할, 책임, 협력이다.

  • 협력
    객체들이 애플리케이션의 기능을 구현하기 위해 수행하는 상호작용
  • 책임
    객체가 협력에 참여하기 위해 수행하는 로직
  • 역할
    객체가 협력 안에서 수행하는 여러 책임들이 모인 집합


협력

객체지향 세계에서 애플리케이션의 기능을 구현하는 유일한 방법은 객체가 서로 협력하는 것이다.
객체는 자신이 맡은 책임에만 집중하며, 그 외 작업은 다른 객체에게 일임하거나 요청한다.
객체간의 협력은 메세지 전송 + 메세지 처리로 이루어지며, 협력에 참여한 객체들은 모두 자율적이다.
결정적으로, 협력은 설계를 위한 문맥을 제시한다는 점에서 반드시 필요하다.
객체는 상태와 행동을 갖는 독립적인 실행 단위이다. 그렇다면 객체의 상태와 행동은 어떻게 결정해야 할까?

영화 예매 시스템 예시를 다시 생각해보자. “영화 예매” 라는 협력 주제 없이 단순히 Movie 객체를 설계하려 한다면, Movie 객체가 play()라는 행동을 갖도록 설계할 수도 있다.
하지만 “영화 예매”를 위한 협력 주제를 정해두면, Movie 객체는 예매 금액을 계산하는 행동만 필요하게 된다.
즉, 객체의 행동은 협력으로부터 파생되고, 객체의 상태는 행동으로부터 파생된다. 협력은 곧 객체의 행동과 상태를 결정한다고 볼 수 있다.


책임

책임이란?

협력이 갖춰졌다면, 협력에 필요한 행동들을 수행할 수 있는 객체를 찾아야 한다.
이 객체가 수행하는 행동들의 집합이 객체의 책임이 된다.
객체의 책임은 “하는 것(doing)” 과 “아는 것(knowing)” 으로 구성된다.
여기서 “하는 것” 은 곧 객체의 행동(메서드) 이 되고, “아는 것”은 객체의 속성(변수) 이 된다.

협력(문맥) 이 결정되면 이를 통해 어떤 행동들이 필요한지를 설계할 수 있고, 그 행동들을 하기 위해선 어떤 정보가 필요한지를 유추할 수 있게 된다.

객체지향 설계에서 가장 중요한 건 책임이다.

책임 할당

자율적인 객체를 만드는 가장 기본적인 방법은, 책임을 수행하는 데 필요한 정보를 가장 잘 알고 있는 전문가에게 그 책임을 할당하는 것이다. 이를 정보 전문가(Information Expert) 패턴이라고 한다.
정보 전문가 패턴으로 영화 예매 시스템을 설계하기 위해선 어떤 메세지들이 오가는지에 중점을 맞춰야 한다. 사용자는 Screening 객체에게 “예매하기” 라는 메세지를 보내고, Screening 객체는 Movie 객체에게 “가격 계산” 이라는 메세지를 보내는 등으로 설계를 하게 된다.
이렇게 설계를 마치면, 객체 간 주고받는 메세지들이 결정되고, 이는 곧 해당 객체의 퍼블릭 인터페이스가 된다.

책임 주도 설계

이와 같이 어떤 객체가 어떤 책임을 가질지 할당하는 방식으로 협력을 설계하는 방법을 책임 주도 설계라고 한다.
책임 주도 설계에서 고려해야 할 사항은 총 두 가지이다.

  • 메세지가 객체를 결정한다.
    메세지가 객체를 결정해야 하는 중요한 두 가지 이유가 있다.
    • 객체가 최소한의 인터페이스를 가질 수 있다.
    • 객체가 충분히 추상적인 인터페이스를 가질 수 있다.
      • 인터페이스는 무엇을 할지 드러내고, 어떻게 할지는 드러내지 않아야 한다.
      • 메세지는 “무엇” 과 관련 있으므로, 메세지를 토대로 인터페이스를 만들면 인터페이스의 본 목적에 맞게 설계할 수 있다.
  • 행동이 상태를 결정한다.
    객체지향 패러다임에 익숙치 않은 사람들이 저지르는 흔한 실수 중 하나는 객체의 행동이 아닌 상태에 초점을 맞춘다. 상태에 초점을 맞추게 되면 상태가 퍼블릭 인터페이스에 노출될 가능성이 높아지고, 이는 캡슐화의 저해를 불러온다. 만약 이 상태에서 객체 내부 구현이 변경되면 퍼블릭 인터페이스도 변경되어야 하고, 이 인터페이스를 사용하는 객체에게 변경의 영향이 전파된다.


역할

지금까지는 객체에게 책임을 할당한다고 했지만, 사실 더 정확히 표현하면 역할에게 책임을 할당하는 것이 맞다. 영화 예매 시스템의 할인 정책 객체 (AmountDiscountPolicy, PercentDiscountPolicy) 와 같이 여러 개의 객체가 동일한 책임을 맡아 수행하는 상황이 많기 때문이다.

역할은 다른 것으로 교체할 수 있는 책임의 집합이다. - Wirfs Brock -

역할을 사용함으로써 훨씬 유연한 협력을 설계할 수 있다.
만약 영화 예매 시스템의 할인 정책 부분을 설계할 때 역할을 사용하지 않는다면 어떻게 될까? 금액 할인 정책과 비율 할인 정책을 위한 협력을 따로 만들어야 한다. 즉, 상당량의 코드 중복이 발생한다.

객체와 역할

정리하자면, 역할은 협력을 수행하기 위한 어떤 책임을 담당하는 슬롯이다. 이 슬롯에는 그 책임을 수행할 수 있는 객체라면 어떤 객체든 대체할 수 있다. 덕분에 역할을 활용하면 더 유연한 설계가 가능하다.
만약 한 종류의 객체만 그 역할을 수행하는 상황이라면, 역할을 고려하는 것이 좋을까? 이런 상황에선 역할을 고려하는게 더 번잡한 작업이 되지는 않을까? 워프스브록은 이에 대해 아래와 같이 말했다.

… 동일한 종류의 객체가 하나의 역할을 항상 수행한다면, 둘은 동일하다. 하지만 어떤 협력에서 하나 이상의 객체가 동일한 책임을 수행할 수 있다면 역할은 서로 다른 방법으로 실행할 수 있는 책임의 집합이 된다. …

다양한 객체들이 협력에 참여하는 것이 확실하다면, 설계 시작부터 역할을 고려하는 것이 좋다. 하지만 그것이 불분명하다면, 일단 구체적인 객체에 책임을 할당하는 것으로 시작하는게 좋다. 다양한 시나리오 및 협력을 고려하다보면 유사한 책임을 갖는 여러 객체들이 보이고, 이는 곧 역할이 된다.

0%