본문 바로가기
Book Record

[Objects] Chapter5. 책임 할당하기

by 그냥팬더 2021. 4. 20.
반응형

05. 책임 할당하기

  • 책임 할당 과정은 일종의 트레이드오프 활동
  • 다양한 책임 할당 방법이 있고, 상황과 문맥에 따라 다르다.

01. 책임 주도 설계를 향해

  • 책임 주도 설계를 위한 두 가지 원칙
    1. 데이터보다 행동을 먼저 결정하라
    2. 협력이라는 문맥 안에서 책임을 결정하라

데이터보다 행동을 먼저 결정하라

  • 객체에게 중요한 것은 외부에 제공하는 행동
    • 책임.
  • 데이터는 책임을 수행하는데 필요한 재료일 뿐
  • 책임 중심의 설계는 이 객체가 수행해야 하는 책임은 무엇인가? 에서 시작한다.
    • 이후 이 책임을 수행하는 데 필요한 정보는 무엇인가? 를 결정한다

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

  • 할당된 책임의 품질은 협력에 적합한 정도로 결정된다.
    • 책임은 객체에 어울리는 게 아니라, 협력에 어울리도록
  • 즉, 메시지 전송자의 의도에 적합한 책임을 할당해야 한다.
    • 메시지를 결정하고 객체를 선택한다.
    • 이때 책임을 할당 받게 된다.
  • 메시지를 먼저 결정하면 수신자에 대해 모르기 때문에 완벽한 캡슐화를 제공
    • 캡슐화는 높은 응집도와 낮은 결합도를 제공한다.

책임 주도 설계

  • 책임을 결정한 후에 책임을 수행할 객체를 결정하는 것.
  • 협력에 참여하는 객체들의 책임이 정리될 때 까지 구현에는 관심을 때는 것.

02. 책임 할당을 위한 GRASP

  • GRASP(General Responsibility Assignment Software Pattern): 일반적인 책임 할당을 위한 패턴
    • 책임을 할당할 때의 지침 같은 원칙을 정리한 것
  • 도메인의 개념들을 정리하는 것으로 시작한다.

도메인 개념에서 출발하기

  • 도메인의 개념을 책임으로 할당하면, 코드에 도메인을 투영하기 쉽다.
  • 다만 설계를 시작하는 것이지, 완벽할 필요는 없다.
    • 많은 시간을 쓰기 보다, 빠르게 설계와 구현을 진행하자.

https://user-images.githubusercontent.com/13096845/87176867-abaa0f80-c315-11ea-86a2-c62faee273dc.png

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

  • 제공해야 하는 기능을 책임으로 생각하고, 책임을 전송된 메시지로 간주하고, 메시지를 책임질 첫 객체를 선택하는 것이 설계의 시작.
  • 영화를 예매하는 기능
    • 시스템은 영화를 예매할 책임이 있다.
    • 메시지를 전송할 객체는 무엇을 원하는가? 예매하라
    • 메시지를 수신할 적합한 객체는 무엇인가? Screening
    • 수행할 정보를 잘 알고 있는 객체: INFORMATION EXPERT(정보 전문가)
      • 다만 알고 있다고, 저장할 필요는 없다
    • 메시지를 받은 Screening은 메시지를 완료하기 위해 스스로 처리 못한다면, 외부에 도움을 요청한다.
      • 이를 통해 협력이 구성된다.
      • 하지만 Screening은 가격을 계산해야 하고, 정보를 모르기 때문에 외부에 요청한다.
      • 외부의 전문가에게 요청한다. 가격을 계산하라
      • 이를 잘 아는 전문가는 Movie
    • 이러한 형태로 계속해서 나아간다.
  • 정보 전문가 패턴은 가장 기본적인 책임 할당 원칙

높은 응집도와 낮은 결합도

  • 설계는 트레이드오프 활동이다.
  • 같은 기능을 구현할 수 있는 많은 설계가 존재한다.
  • 만약 Screening이 직접 DiscountCondition과협력한다면?
    • 제공하는 기능은 동일하다.
    • 하지만 응집도와 결합도의 차이가 발생한다.
  • 다수의 협력 패턴이 존재한다면 높은 응집도와 낮은 결합도를 얻을 수 있는 설계를 선택한다.

LOW COUPLING(낮은 결합도) 패턴
설계의 전체적인 결합도가 낮게 유지되도록 책임을 할당하는 것

  • 원래의 시스템에서 이미 MovieDiscountCondition은 결합돼있다.
    • 따라서 두 객체가 협력한다면, 결합도는 추가되지 않는다.
    • 하지만, Screening이 직접 DiscountCondition과 협력하면 새로운 결합도가 추가된다.

HIGH COHESION(높은 응집도) 패턴
높은 응집도를 유지할 수 있게 책임을 할당하는 것

  • 중요한 책임은 예매를 생성하는 것
    • 만약 Screening이 직접 DiscountCondition과 협력하면 Screening은 요금 계산의 책임 일부를 받는다.
      • 예매 요금 계산 방식이 바뀌면 Screening도 바뀐다.
    • 결과적으로, 서로 다른 이유로 변경되는 책임을 짊어져 응집도가 낮아진다.
  • 다양한 설계에서 높은 응집도, 낮은 결합도를 고려하면 유연한 설계를 얻을 수 있다.

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

  • 영화 예매 시스템의 최종 결과물은 Reservation을 생성하는 것
  • 즉 누군가는 생성의 책임을 할당해야 한다.
  • GRASP의 CREATOR 패턴은 객체 생성 책임을 어떤 객체에 할당할지 안내한다.
    • 객체 A를 생성해야 하는 책임을 할당할 객체B를 선정하는 방법
      • B가 A를 포함하거나 참조한다.
      • B가 A를 기록한다.
      • B가 A를 긴밀하게 사용한다.
      • B가 A를 초기화하는 데 필요한 데이터를 가지고 있다.(B는 A의 정보전문가)
    • 이 중 많은 조건을 만족하는 객체 B를 선택한다.
  • Reservation의 경우 위 조건을 만족하는 객체는 Screening이다.
    • 생성하는데 있어서 필요한 정보에 대한 정보 전문가다.

03. 구현을 통한 검증

  • 구현에 숨은 몇가지 문제점

DiscountCondition 개선하기

  • 변경에 취약한 클래스를 포함하고 있다.
    • 코드를 수정하는 이유가 한 개 이상인 클래스
    • 응집도가 낮다.
  • DiscountCondition은 세 가지 이유로 변경될 수 있다.
    1. 새로운 할인 조건 추가
      • isSatisfiedBy안의 if - else를 변경해야 한다.
    2. 순번 조건을 판단하는 로직 변경
    3. 기간 조건을 판단하는 로직 변경
  • 따라서, 변경의 이유에 따라 클래스를 분리해야 한다.
  • 변경의 이유를 빠르게 파악하는 방법
    1. 인스턴스 변수가 초기화 되는 시점
      • 응집도가 높은 경우 생성에 모든 속성을 초기화
      • 낮은 경우에는 일부는 초기화 되지 않은 상태로 남겨진다.
      • 함께 초기화 되는 속성을 기준으로 코드를 분리한다.
    2. 메서드들이 인스턴스 변수를 사용하는 방식
      • 응집도가 높은 경우 모든 메서드가 객체의 모든 속성을 사용한다.
      • 낮은경우에는 속성에 따라 그룹이 나뉜다.
      • 속성 그룹과 해당 그룹에 접근하는 메서드 그룹을 기준으로 코드를 분리한다.

타입 분리하기

  • 가장 큰 문제는 조건들이 하나의 클래스 안에 공존하고 있다.
    • 따라서, 두 타입을 두 개의 클래스로 분리하는 것.
    • 분리를 통해 코드의 품질은 높였지만 다른 문제를 야기했다.
  • 수정 전에는 MoiveDiscountCondition하나랑 협력했다.
  • 수정 후에는 SequenceCondition, PeriodCondition 두 개와 모두와 협력해야 한다.
    • 이를 위해 Moive가 두 목록을 따로 유지할 수 있다.
      • 하지만, 클래스 양쪽 모두와 결합한다는 문제가 발생한다.
        • 결합도가 높아진 것
    • 또한, 새로운 조건을 추가하기 어려워졌다.
  • 응집도 측면에선 개선됐지만, 변경과 캡슐화에서 바라보면 설계의 품질이 하락햇다.

다형성을 통해 분리하게

  • Moive의 입장에선 두 조건이 큰 차이가 없다.
    • 그저 할인 여부를 판단하는 동일한 책임을 수행한다.
    • 방법이 다른 것은 크게 중요치 않다.
  • 이 시점에서 역할이 나타난다.
    • 두 조건은 동일한 책임, 동일한 역할을 수행한다.
  • 역할을 사용하면 객체의 구체적인 타입을 추상화 할 수 있다.
    • 자바에서는 추상 클래스와 인터페이스로 제공한다.
    • 구현을 공유한다면 추상 클래스를 책임만을 원한다면 인터페이스를
  • 이를 POLYMORPHISM(다형성) 패턴이라 한다.

POLYMORPHISM 패턴
타입을 명시적으로 정의하고, 각 타입에 다형적으로 행동하는 책임을 할당한다.

변경으로부터 보호하기

  • 만약 새로운 할인 조건이 추가 된다면?
    • 역할을 통해 Movie에서 DiscountCondition은 캡슐화된다.
    • 새로운 타입을 추가하더라도 Movie는 영향을 받지 않는다.
  • 변경을 캡슐화하도록 책임을 할당하는 것을 PROTECTED VARIATIONS(변경 보호) 패턴이라 한다.

PROTECTED VARIATIONS 패턴
변화가 예상되는 불안정한 지점들을 식별하고, 그 주위에 안정된 인터페이스를 형성하게 책임을 할당한다.

  • 하나의 클래스가 여러 타입의 행동을 구현한다면 클래스를 분리하고, POLYMORPHISM 패턴으로 책임을 분산시킨다.
  • 예측 가능한 변경으로 여러 클래스가 불안정해진다면, PROTECTED VARIATIONS 패턴으로 안정적인 인터페이스 뒤로 변경을 캡슐화한다.
  • 이를 통해 코드 수정의 파급효과를 제어할 수 있고, 유연한 설계를 얻을 수 있다.

변경과 유연성

  • 설계를 주도하는 것은 변경
  • 개발자로서 변경에 대비할 수 있는 방법
    1. 코드를 이해하고 수정하기 쉽도록 최대한 단순하게 설계하기
    2. 변경을 수용할 수 있도록 코드를 유연하게 만들기
  • 대다수의 경우 1번이 더 좋지만, 2번을 선택해야 하는 경우가 있다.
    • 만약 할인 정책을 변경해야 한다면?
      • 지금은 상속을 사용하고 있어, 새로운 기능을 추가되면 인스턴스를 생성하고, 복사하고 등등 비효율적인 일들을 한다.
      • 이때에는 합성을 사용해 유연하게 만든다.
  • 유연성에 대한 압박이 설계에 영향을 미치고, 유연성은 결국 의존성의 정도다.

04. 책임 주도 설계의 대안

  • 책임을 빨리 찾는 방법은 빠르게 목적하는 기능을 수행하는 코드를 작성하는 것.
    • 일단 코드를 얻고 책임을 올바른 위치로 할당하는 것이 중요하다.
    • 다만, 외부에 제공하는 동작이 바뀌는 것이 아니다.
      • 캡슐화를 시키고, 응집도를 높이고, 결합도를 낮추지만 동작은 유지
      • 리팩토링(Refactoring)이라 한다

메서드 응집도

  • 데이터 중심의 시스템에서의 객체들은 단지 데이터의 집합일 뿐
    • 책임을 분배하면 책임 주도 설계와 유사할 것이다.
  • 메서드의 길이가 길고 이해하기 어렵다면
    • 응집도가 낮기때문에 유지보수 및 변경이 어렵다.
    • 이를 몬스터 메서드(Monster Method)라고 한다.
  • 로직이 이해하기 어려워 주석이 필요하다면?
    • 응집도가 낮은 메서드임을 자명하는 것.
    • 메서드를 분리해 응집도 높이는 것이 필요하다.
  • 이를 통해 이해하기 쉬운 좋은 코드가 만들어진다.

객체를 자율적으로 만들자

  • 어떤 책임을 어떤 객체에 할당하는가?
    • 객체는 자율적인 존재여야 한다.
    • 자신의 데이터를 스스로 처리하도록 만드는 것
  • 책임 주도 설계가 어색하다면
    • 데이터 중심의 설계를 만들고 리팩터링을 거쳐 책임 주도 설계로 바꿔라.
반응형

댓글