ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 오브젝트 - Chapter 05 책임 할당하기
    Study 2023. 10. 22. 16:13
    • 책임에 초점을 맞춰서 설계할 때 가장 큰 어려움은 어떤 객체에 어떤 책임을 할당할지를 결정하기 쉽지 않다는 것이다.

    01 책임 주도 설계를 향해

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

    • 책임 중심 설계에서는 "이 객체가 수행해야 하는 책임은 무엇인가"를 결정한 후에 "이 책임을 수행하는 데 필요한 데이터(상태)는 무엇인가"를 결정한다.

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

    • 협력에 적합한 책임이란 메시지 수신자가 아니라 메시지 전송자에게 적합한 책임을 의미한다.
    • 협력에 적합한 책임을 할당하기 위해서는 메시지를 결정한 후에 객체를 선택해야 한다.
    • 메시지를 수신하기로 결정된 객체는 메시지를 처리할 책임을 할당 받게 된다.
    • 협력이라는 문맥 안에서 메시지에 집중하는 책임 중심 설계는 캡슐화의 원리를 지키기가 훨씬 쉬워진다.

    책임 주도 설계

    • 시스템이 사용자에게 제공해야 하는 기능인 시스템 책임을 파악한다.
    • 시스템 책임을 더 작은 책임으로 분할한다.
    • 분할된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아 책임을 할당한다.
    • 객체가 책임을 수행하는 도중 다른 객체의 도움이 필요한 경우 이를 책임질 적절한 객체 또는 역할을 찾는다.
    • 해당 객체 또는 역할에게 책임을 할당함으로써 두 객체가 협력하게 된다.

    책임 주도 설계의 핵심은 책임을 결정한 후에 책임을 수행할 객체를 결정하는 것이다. 그리고 협력에 참여하는 객체들의 책임이 어느 정도 정리될 때까지는 객체의 내부 상태에 대해 관심을 가지지 않는 것이다.

    02 책임 할당을 위한 GRASP 패턴

    • GRASP은 "General Responsibility Assignment Software Pattern(일반적인 책임 할당을 위한 소프트웨어 패턴)"의 약자로 객체에게 책임을 할당할 때 지침으로 삼을 수 있는 원칙들의 집합을 패턴 형식으로 정리한 것이다.

    도메인 개념에서 출발하기

    • 설계를 시작하기 전에 도메인에 대한 개략적인 모습을 그려 보는 것이 유용한다.
    • 도메인 안에는 무수히 많은 개념들이 존재하며 이 도메인 개념들을 책임 할당의 대상으로 사용하면 코드에 도메인의 모습을 투영하기가 좀 더 수월해진다.
    • 올바른 도메인 모델이란 존재하지 않는다. -> 도메인을 구현하는데 도움이 되는 실용적이면서 유용한 모델이라면 모두 맞다.

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

    • 객체에게 책임을 할당하는 첫 번째 원칙은 책임을 수행할 정보를 알고 있는 객체에게 책임을 할당하는 것이다. GRASP에서는 이를 INFORMATION EXPERT(정보 전문가) 패턴이라고 부른다.
    • 여기서 이야기하는 정보는 데이터와 다르다는 사실에 주의하라. 책임을 수행하는 객체가 정보를 '알고' 있다고 해서 그 정보를 '저장'하고 있을 필요는 없다.
    • 책임을 수행하는데 필요한 작업을 구상해 보고 스스로 처리할 수 없는 작업이 무엇인지를 파악하고
      만약 스스로 처리할 수 없는 작업이 있다면 외부에 도움을 요청해야 한다.
      이 요청이 외부로 전송해야 하는 새로운 메시지가 되고, 최종적으로 이 메시지가 새로운 객체의 책임으로 할당된다.
      이 같은 연쇄적인 메시지 전송과 수신을 통해 협력 공동체가 구성되는 것이다.
    • INFORMATION EXPERT 패턴을 따르는 것만으로도 자율성이 높은 객체들로 구성된 협력 공동체를 구축할 가능성이 높아진다.

    높은 응집도와 낮은 결합도

    • 설계는 트레이드오프 활동이라는 것을 기억하라.
      동일한 기능을 구현할 수 있는 무수히 많은 설계가 존재한다.
    • 높은 응집도와 낮은 결합도는 객체에 책임을 할당할 때 항상 고려해야 하는 기본 원리다.
    • GRASP에서는 이를 LOW COUPLING(낮은 결합도) 패턴과 HIGH COHESION(높은 응집도) 패턴이라고 부른다.
    • 책임을 할당하고 코드를 작성하는 매순간마다 LOW COUPLING과 HIGH COHESION의 관점에서 전체적인 설계 품질을 검토하면 단순하면서도 재사용 가능하고 유연한 설계를 얻을 수 있다.

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

    • GRASP의 CREATOR(창조자) 패턴은 객체를 생성할 책임을 어떤 객체에게 할당할지에 대한 지침을 제공한다.

    CREATOR 패톤

    • 객체 A를 생성해야 할 때 어떤 객체에게 객체 생성 책임을 할당해야 할까? 아래 조건을 최대한 많이 만족하는 B에게 객체 생성 책임을 할당하라
      • B가 A 객체를 포함하거나 참조한다.
      • B가 A 객체를 기록한다.
      • B가 A 객체를 긴밀하게 사용한다.
      • B가 A 객체를 초기화하는데 필요한 데이터를 가지고 있다(이 경우 B는 A에 대한 정보 전문가다)
    • CREATOR 패턴의 의도는 어떤 방식으로든 생성되는 객체와 연결되거나 관련될 필요가 있는 객체에 해당 객체를 생성할 책임을 맡기는 것이다. 생성될 객체에 대해 잘 알고 있어야 하거나 그 객체를 사용해야 하는 객체는 어떤 방식으로든 생성될 객체와 연결될 것이다.
      다시 말해 두 객체는 서로 결합된다.
    • 이미 결합돼 있는 객체에게 생성 책임을 할당하는 것은 설계의 전체적인 결합도에 영향을 미치지 않는다.
    • 결과적으로 CREATOR 패턴은 이미 존재하는 객체 사이의 관계를 이용하기 때문에 설계가 낮은 결합도를 유지할 수 있게 한다.

    03 구현을 통한 검증

    • 메시지(함수)는 송신자의 의도를 표현한다.
    • 코드 내용은 책 146-151 같이 보기

    DiscountCondition 개선하기

    • 가장 큰 문제점은 변경에 취약한 클래스를 포함하고 있다는 것이다.
    • 변경에 취약한 클래스란 코드를 수정해야 하는 이유를 하나 이상 가지는 클래스다.
    • DiscountCondition이 변경될 수 있는 서로 다른 3 가지 이유
      • 새로운 할인 조건 추가
      • 순번 조건을 판단하는 로직 변경
      • 기간 조건을 판단하는 로직 변경
    • DiscountCondition은 하나 이상의 변경 이유를 가지기 때문에 응집도가 낮다.
    • 따라서 낮은 응집도가 초래하는 문제를 해결하기 위해서는 변경의 이유에 따라 클래스를 분리해야 한다.
    • 일반적으로 설계를 개선하는 작업은 변경의 이유가 하나 이상인 클래스를 찾는 것으로부터 시작하는 것이 좋다.
    • 변경의 이유가 하나 이상인 클래스에는 위험 징후를 또렷하게 드러내는 몇 가지 패턴이 존재한다.
      • 파악 방법 1: 인스턴스 변수가 초기화되는 시점
        • 응집도가 높은 클래스는 인스턴스를 생성할 때 모든 속성을 함께 초기화한다.
        • 함께 초기화되는 속성을 기준으로 코드를 분리해야 한다.
      • 파악 방법 2: 메서드들이 인스턴스 변수를 사용하는 방식
        • 응집도를 높이기 위해서는 속성 그룹과 해당 그룹에 접근하는 메서드 그룹을 기준으로 코드를 분리해야 한다.

    클래스 응집도 판단하기

    • 클래스가 다음과 같은 징후를 보인다면 클래스의 응집도는 낮은 것이다.
      • 클래스가 하나 이상의 이유로 변경돼야 한다면 응집도가 낮은 것이다. -> 변경의 이유를 기준으로 클래스를 분리하라.
      • 클래스의 인스턴스를 초기화하는 시점에 경우에 따라 서로 다른 속성들을 초기화하고 있다면 응집도가 낮은 것이다.
        -> 초기화되는 속성의 그룹을 기준으로 클래스를 분리하라.
      • 메서드 그룹이 속성 그룹을 사용하는지 여부로 나뉜다면 응집도가 낮은 것이다. -> 이들 그룹을 기준으로 클래스를 분리하라.

    타입 분리하기

    • DiscountCondition의 가장 큰 문제는 순번 조건과 기간 조건이라는 두 개의 독립적인 타입이 하나의 클래스 안에 공존하고 있다는 점이다.
    • 클래스를 분리하면 앞에서 언급했던 문제점들이 모두 해결된다.
    • 그런데 클래스를 분리하고 나면 Movie의 인스턴스는 SequenceCondition과 PeriodCondition이라는 두 개의 서로 다른 클래스의 인스턴스 모두와 협력할 수 있어야 한다.
      클래스를 분리한 후에 설계의 관점에서 전체적인 결합도가 높아진 것이다.
      (DiscountCondition -> SequenceCondition과 PeriodCondition)
    • 그리고 새로운 할인 조건을 추가하기가 더 어려워졌다. 수정 후에는 할인 조건을 추가하려면 Movie도 함께 수정해야 한다.
      DiscountCondition의 입장에서 보면 응집도가 높아졌지만 변경과 캡슐화라는 관점에서 보면 전체적으로 설계의 품질이 나빠지고 만 것

    다형성을 통해 분리하기

    • 사실 Moive의 입장에서 보면 SequenceCondition과 PeriodCondition은 아무 차이도 없다.
    • Moive 입장에서는 동일한 책임을 수행한다는 것은 동일한 역할을 수행한다는 것을 의미한다.
    • 역할을 사용하면 객체의 구체적인 타입을 추상화(Interface or Abstract)할 수 있다.
    • GRASP에서는 이를 POLYMORPHISM(다형성) 패턴이라고 부른다.

    변경으로부터 보호하기

    • DiscountCondition의 두 서브클래스는 서로 다른 이유로 변경된다는 사실을 알 수 있다.
      서로 다른 변경이 두 개의 서로 다른 클래스 안으로 캡슐화된다.
    • 변경을 캡슐화하도록 책임을 할당하는 것을 GRASP에서는 PROTECTED VARIATIONS(변경 보호) 패턴이라고 부른다.

    변경과 유연성

    • 설계를 주도하는 것은 변경이다.
    • 개발자로서 변경에 대비할 수 있는 두 가지 방법이 있다.
      • 코드를 이해하고 수정하기 쉽도록 최대한 단순하게 설계하는 것이다.
      • 코드를 수정하지 않고도 변경을 수용할 수 있도록 코드를 더 유연하게 만드는 것이다.
    • 합성을 통해서 코드를 유연하게 만들 수 있다. (164p 그림 5.8)
    • 도메인 모델은 코드의 변화에 발맞춰 함께 변화해야 한다.
      도메인 모델을 코드와 분리된 막연한 무엇으로 생각하지 않기 바란다.

    04 책임 주도 설계의 대안

    메서드 응집도

    • 긴 메서드는 응집도가 낮기 때문에 이해하기도 어렵고 재사용하기도 어려우며 변경하기도 어렵다.
      이런 메서드를 몬스터 메서드라고 부른다.
    • 메서드를 작게 분해해서 각 메서드의 응집도를 높여라.
    • 메서드의 응집도를 높이는 이유도 변경과 관련이 깊다.
      응집도 높은 메서드는 변경되는 이유가 단 하나여야 한다.

    객체를 자율적으로 만들자

    • 어떤 메서드를 어떤 클래스로 이동시켜야 할까?
    • 자신이 소유하고 있는 데이터를 자기 스스로 처리하도록 만든 것이 자율적인 객체를 만드는 지름길이다.
      따라서 메서드가 사용하는 데이터를 저장하고 있는 클래스로 메서드를 이동시키면 된다.
    • 데이터 중심으로 코드를 작성하고 리팩터링 하더라도 캡슐화, 결합도, 응집도를 이해하고 객체지향 원칙을 적용하기 위해 노력한다면 책임 주도 설계와 유사한 결과를 얻을 수 있다.

     

    반응형

    댓글

Designed by Tistory.