5. 책임 할당하기

책임 주도 설계를 향해

  • 데이터 중심 => 책임 중심
    1. 데이터보다 행동을 먼저 결정하라
    2. 협력이라는 문맥 안에서 책임을 결정하라

협력이라는 문맥 안에서 책임을 결정하라

  • 책임은 객체의 입장이 아니라 객체가 참여하는 협력에 적합해야 한다.
    • 메시지를 전송하는 클라이언트의 의도에 적합한 책임을 할당해야 한다
    • 메시지가 객체를 선택하게 해야 한다
  • 클래스 기반 설계에서 메시지 기반 설계로 전환해야 한다
    • “이 클래스가 무엇을 해야 하지?” => “메시지를 전송해야 하는데 누구에게 전송해야 하지”
    • 메시지를 전송하기 때문에 객체를 갖게 된다
  • 메시지를 수신하기로 결정된 객체는 메시지를 처리할 책임을 할당 받게 된다
  • 메시지를 먼저 결정하기에, 메시지 전송자의 관점에서 수신자가 깔끔하게 캡슐화 된다

책임 할당을 위한 GRASP 패턴

  • General Responsibility Assignment Software Pattern: 크레이크 라만이 패턴 형식으로 제안

도메인 개념에서 출발하기

  • 도메인 개념들을 책임 할당의 대상으로 사용하면 코드에 도메인의 모습을 투영하기가 좀 더 수월해진다.
  • 도메인 개념을 정리하는 데 너무 많은 시간을 들이지 말고 빠르게 설계와 구현을 진행하라
    • 올바른 도메인 모델이란 존재하지 않는다

정보 전문가에게 책임을 할당하라

  • 책임을 전송된 메시지로 간주하고, 메시지를 책임질 첫 번째 객체를 선택하는 것으로 설계를 시한다

    • Ex) 애플리케이션은 영화를 예매할 책임이 있다.
  • 첫 번째 질문: 메시지를 전송할 객체는 무엇을 원하는가?

    • 메시지의 이름으로 “예매하라” 가 적절하다
  • 두 번째 질문: 메시지를 수신할 적합한 객체는 누구인가?

    • 책임을 수행할 정보를 알고 있는 객체에게 책임을 할당=> 정보 전문가(Information Expert) 패턴

    • 가장 기본적인 책임 할당 원칙

    • 책임을 수행하는 데 필요한 정보를 가지고 있는 객체에게 할당하라

    • 정보 전문가는 정보를 알고 있지만, 그 정보를 꼭 데이터로 저장하고 있을 필요는 없다

높은 응집도와 낮은 결합도

  • 객체에 책임을 할당할 때 다양한 대안이 존재한다면 높은 응집도와 낮은 결합도 측면에서 생각해라
  • Low Coupling, High Cohesion 패턴이라고 한다
  • Low Coupling
    • 의존성을 낮추고 변화의 영향을 줄이며 재사용성을 증가시킬 수 있는 방법
      • 설계의 전체적인 결합도가 낮게 유지되도록 책임을 할당하라
    • MovieDiscountCondition은 이미 결합돼 있다.
    • ScreeningDiscountCondition이 협력 할 경우 새로운 결합도가 생성된다.
    • 따라서 MovieDiscountCondition이 렵력하는 것이 더 나은 설계 대안이다.
  • High Cohesion
    • 어떻게 복잡성을 관리할 수 있는 수준으로 유지하는 법: 높은 응집도를 유지할 수 있게 책임을 할당해라
    • ScreeningDiscountCondition이 협력 할 경우
      • Screening은 영화 요금 계산과 관련된 책임 일부를 떠안아야 한다.
      • 예매 요금을 계산하는 방식이 변경될 경우 Screening도 함께 변경되어야 한다
      • 서로 다른 이유로 변경되는 책임을 짊어지게 되므로 응집도 낮아짐

창조자에게 객체 생성 책임을 할당하라

  • 영화 예매 협력의 최종 결과물은 Reservation 인스턴스를 반환하는 것
  • Creator(창조자) 패턴은 객체를 생성할 책임을 어떤 객체에게 할당하지에 대한 지침을 제공
  • 객체 A가, 아래 조건을 최대한 만족하는 B에게 객체 생성 책임을 할당해라
    • B가 A 객체를 포함하거나 참조한다.
    • B가 A 객체를 기록한다
    • B가 A 객체를 긴밀하게 사용한다
    • B가 A 객체를 초기화하는 데 필요한 데이터를 가지고 있다 (B는 A에 대한 전문가다)
  • 이미 결합돼 있는 객체에게 생성 책임을 할당하는 것은 설계의 전체적인 결합도에 영향을 미치지 않는다.
    • 이미 존재하는 객체 사이의 관계를 이용하기에 낮은 결합도를 유지할 수 있다.

구현을 통한 검증

DiscountCondition 개선하기

  • DiscountCondition 은 서로 다른 세 가지 이유로 변경될 수 있다.

    1. 새로운 할인 조건 추가
    2. 순번 조건을 판단하는 로직 변경
    3. 기간 조건을 판단하는 로직이 변경되는 경우
  • DiscountCondition 은 하나 이상의 변경 이유를 가지기 때문에 응집도가 낮다

    • 변경에 이유에 따라 클래스를 분리해야 한다
  • 코드를 통해 변경의 이유를 파악할 수 있는 방법

    1. 변경 이유가 하나 이상의 이유로 변경돼야 한다면 응집도가 낮은 것

    2. 인스턴스 변수가 초기화되는 시점을 살펴보는 것

      • 응집도가 높은 클래스는 인스턴스를 생성할 때 모든 속성을 함께 초기화한다
    3. 메서드들이 인스턴스 변수를 사용하는 방식

      • 모든 메서드가 객체의 모든 속성을 사용한다면 클래스의 응집도는 높다고 볼 수 있다

타입 분리하기

  • DiscountCondition 을 순번 조건과 기간 조건이라는 두 개의 독립적인 타입으로 변경
    • 응집도가 높아지지만, 캡슐화라는 관점에서 설계 품질이 나빠졌음

다형성을 통해 분리하기

  • 객체의 암시적인 타입에 따라 행동을 분기해야 한다면 암시적인 타입을 명시적인 클래스로 정의하고 행동을 나눔으로써 응집도 문제를 해결할 수 있다
    • DiscountCondition 이라는 인터페이스를 구현하는 형식으로 변경
    • 타입을 분리하고 변화하는 행동을 각 타입의 책임으로 할당해라
    • Polymoorphism(다형성) 패턴
    • 객체의 타입을 검사해서 타입에 따라 대안들을 수행하는 조건적인 논리를 사용하지말고, 다형성을 이용해 변화를 다루기 쉽게 확장해라

변경으로부터 보호하기

  • Movie의 관점에서 DiscountCondition 의 타입이 캡슐화된다는 것은 새로운 DiscountCondition 타입이 추가되더라도 Movie가 영향을 받지 않는다는 것을 의미
    • 오직 DiscountCondition 인터페이스를 실체화하는 클래스를 추가하는 것으로 할인 조건의 종류를 확장할 수 있다.
  • 캡슐화하도록 책임을 할당하는 것을 Protected Variations(변경 보호) 패턴 이라 한다.
    • 변경될 가능성이 높으면, 캡슐화 하라

Movie 클래스 개선하기

  • 도메인의 구조가 코드의 구조를 이끈다
    • 변경 역시 도메인 모델의 일부이다.
    • 도메인 모델에는 도메인 안에서 변하는 개념과 이들 사이의 관계가 투영돼 있어야 한다.

변경과 유연성

  • 설게를 주도하는 것은 변경이다; 개발자로서 변경에 대비할 수 있는 방법 두 가지

    • 이해하고 수정하기 쉽게 최대한 단순하게 설계
    • 코드를 유연하게 만드는 것
  • 상속 대신 합성을 사용하면, 새로운 정책이 추가될 때 마다 유연한 설계가 가능하다

  • 도메인 모델은 단순히 도메인의 개념과 관계를 모아 놓은 것이 아니다.

    • 도메인 모델은 구현과 밀접한 관계를 맺어야 한다.

책임 주도 설계의 대안

  • 수정하기 쉬운 소프트웨어로 개선하기 위해 겉으로 보이는 동작은 바꾸지 않은 채 내부 구조를 변경하는 것을 리팩토링 이라 한다.

    • 겉으로 드러나는 동작이 바꿔서는 안된다

    • 캡슐화, 응집도, 결합도 관점에서 개선하고 동작은 그대로 유지해야 한다

메서드 응집도

  • 몬스터 메서드(monster method): 코드를 재사용하는 유일한 방법이 원하는 부분을 복사 붙여넣는 것 뿐인, 변경하기 어려운 긴 메서드
  • 클래스의 응집도와 마찬가지로 응집도가 높은 메서드는 변경되는 이유가 단 하나이다.
    • 작은 메서드들로 조합된 메서드는 마치 주석들을 나열한 것처럼 보이기에 코드를 이해하기 쉽다.

객체를 자율적으로 만들자

  • 메서드를 다른 클래스로 이동시킬 때는 인자에 정의된 클래스 중 하나로 이동하는 경우가 일반적이다.

Reference