[오브젝트] chapter 05. 책임 할당하기

5 minute read

책임 주도 설계를 향해

두 가지 원칙

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

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

책임 중심의 설계에서는 객체의 행동, 즉 책임을 먼저 결정한 후에 객체의 상태를 결정한다는 것이다.

그러기에 객체지향 설계에서 가장 중요한 것은 적절한 책임을 할당하는 능력이다.

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

책임은 객체의 입장이 아니라 객체가 참여하는 협력에 적합해야 한다.

메시지를 전송하는 클라이언트의 의도에 적합한 책임을 할당해야 한다.

객체가 메시지를 선택하는 것이 아니라 메시지가 객체를 선택해야 한다.

여기서 메시지에 집중해야 되는 것은 메시지는 클라이언트의 의도이다. 클라이언트는 해당 메시지를 통해서 자신의 의도를 표현하고 그렇게 행동하기를 원하기 때문에 해당 객체는 메시지를 처리할 책임을 할당받게 된다.

책임 할당을 위한 GRASP 패턴

  • GRASP 패턴
    • General Responsibility Assignment Software Patterns
    • 일반적인 책임 할당을 위한 소프트웨어 패턴

도메인 개념에서 출발하기

설계를 시작하기 전에 도메인에 대한 개략적인 모습을 그려 보는 것이 유용.

도메인 개념들을 잘 알고 이해하게 되면 자연스럽게 코드에 도메인의 모습을 투영할 수 있기 때문에 그리고 그것을 통해서 책임을 잘 할당할 수 있기 때문이다.

도메인 모델

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

메시지가 있다. 해당 메시지를 수행해야되는 책임을 받을 객체를 선정하기 위해서는 어떻게 해야될까? 가장 적합한 친구는 누구일까?

이 질문의 답으로 객체가 상태와 행동을 통합한 캡슐화의 단위라는 사실에 집중해야 한다. 스스로 처리할 수 있는 자율적인 존재여야 한다.

그러기에 책임을 수행할 정보를 알는 객체에서 책임을 할당하자. -> 정보 전문가 패턴

반드시 모든 정보를 가지고 있을 필요는 없다. 내가 가지고 있지 않아도 해당 정보를 아는 객체를 알고 있는 것만으로도 충분하다.

낮은 결합도

결합도: 어떤 모듈이 다른 모듈에 의존하는 정도를 나타내는 것이다.

하나의 객체가 여러 객체와 결합하고 있으면 변경에 있어서 자유롭지 못하다. 하나의 변경이 곧 여러 컴포넌트의 변경으로 전파될 수 있기 때문이다.

높은 응집도

응집도: 모듈 내부의 기능적인 응집 정도를 나타낸다.

오직 하나의 기능만을 위해서 구성되어진 경우 변경에 대해서 다른 컴포넌트에 영향을 끼치지 않는다.

하지만 높은 응집도를 이루지 못한 경우 다른 컴포넌트에서 해당 책임을 담당하게 될 것이고 그것은 변경에 대해서 전파되게 된다.

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

어떤 방식이로든 생성되는 객체와 연결되거나 관련될 필요가 있는 객체에 해당 객체를 생성할 책임을 맡기는 것이다.

생성될 객체에 대해서 알고 있다는 것은 결합되어있을 확률이 매우 높고 이미 결합되어 있는 객체에서 책임을 할당하는 것은 전체적인 결합도에 영향을 주지 않는다. => 이미 존재하는 객체 사이의 관계를 이용해서 낮은 결합도를 유지할 수 있게 한다.

우리의 목표는 Reservation을 만드는 것. 그것에 대한 정보를 가장 많이 알고 있고 생성에 대한 정보를 가지고 있는 친구는 Screening이기에 해당 객체에 책임을 할당

구현을 통한 검증

실제로 코드를 짜보자~

DiscountCondition 개선하기

코드르 보게 되면 3가지 이유로 인해서 변경이 될 수 있다.

  • 새로운 할인 조건 추가
    • if~else 문으로 되어 있고 그것은 새로운 것이 추가될때 마다 계속 변경이 됨.
  • 순번 조건 판단하는 로직 변경
  • 기간 조건 판단하는 로직 변경

여러 이유로 변경될 수 있다. => 응집도가 낮다. => 클래스를 분리해야 된다.

isSatisfiedBySequence 메소드 와 isSatisfiedByPeriod 메소드의 변경 시점은 다를 수 밖에 없다.

어떻게 코드 변경의 이유를 파악할 수 있을까?

  1. 인스턴스 변수가 초기화되는 시점
    • 응집도가 높은 클래스는 인스턴스가 생성될 때 모든 속성을 함께 초기화. 그렇지 않은 경우는 일부만 초기화하고 일부는 초기화되지 않은 상태로
    • sequence가 초기화될 때 다른 변수는 초기화 안됨.
    • 같이 초기화 되는 애들끼리 클래스 분리
  2. 메서드들이 인스턴스 변수를 사용하는 방식
    • 메소드에서 모든 속성을 사용한다면 높은 응집도. 아닌 경우는 응집도 낮은 경우
    • 속성 그룹과 해당 그룹에 접근하는 메소드 그룹을 기준으로 코드를 분리

응집도가 낮은 힌트

  • 클래스가 하나 이상의 이유로 변경돼는 경우
  • 인스턴스 초기화시점에 서로 다른 속성들이 초기화
  • 메서드 그룹이 속성 그룹을 사용하는지 여부

타입 분리하기

가장 큰 문제는 순번 조건과 기간 조건이라는 두 개의 독립적인 타입이 하나의 클래스 안에 공존하고 있다는 점.

=> DiscountCondition => PeriodCondition, SequenceCondition 이렇게 분리할 수 있다.

  • 분리하고 나니 새로운 문제가 나타난다.
    1. Movie 클래스가 분리된 클래스 모두에게 결합
      • 이전에는 하나의 클래스에게만 결합되어 있었지만 분리되면서 모두에게 결합되는 문제가 발생.
    2. 수정 후에 새로운 할인 조건 추가 어려움
      • List로 관리되어 있던 곳에 추가해야 되고, 만족 여부에 대한 메서드를 추가하고, 기존에 있던 메소드도 수정해야 됨.

=> 전체적으로 결합도가 너무 올라갔고 오히려 캡슐화가 깨지게 되면서 품질이 떨어짐.

다형성을 통해 분리하기

해당 클래스오 협력하고 있는 Movie라는 객체는 어떤 클래스인지 중요하지 않다. 그러기에 역할이라는 개념을 활용해서 동일한 책임을 수행하는 두개의 클래스를 하나의 역할로 묶어버린다.

Movie 클래스는 오직 역할에 대해서만 알고 있지 실제 그 역할을 실행한 인스턴스는 중요하지 않게 됨.

public interface Condition {
    boolean isSatisfiedBy(Screening screening);
}

---

public class PeriodCondition implements Condition {
    @Override
    boolean isSatisfiedBy(Screening screening) {
        return true;
    }
}

public class SequenceCondition implements Condition {
    @Override
    boolean isSatisfiedBy(Screening screening) {
        return true;
    }
}

객체의 타입에 따라 변하는 행동이 있다면 타입을 분리하고 변화하는 행동을 각 타입의 책임으로 할당하는 것. => 다형성 패턴

변경으로부터 보호하기

우리는 해당 인터페이스를 통해서 어떤 특정 condition이 추가되거나 내부적으로 수정된다고 해도 완벽하게 해당 책임을 이루고 다른 변경에도 문제가 되지가 않는다.

변경을 캡슐화하도록 책임을 할당하는 것을 변경 보호 패턴이라고 한다.

  • 하나의 클래스가 여러 타입의 행동을 구현하고 있는 것처럼 보인다면 사용해라.
  • 예측 가능한 변경으로 인해 여러 클래스들이 불안정해진다면 안정저긴 인터페이슬르 사용해서 캡슐화 시켜라.

Movie 클래스 개선하기

동일한 문제를 Movie 클래스에서도 있다. 클래스 내부에 똑같은 두가지 타입을 하나의 클래스 안에서 구현하고 있기 때문이다. => 변경되는 시점이 다르다.

해결방법은 다형성 패턴이다. 해당 패턴을 통해서 서로 다른 행동을 타입별로 분리하여 사용할 수 있다.

그 뿐만 아니라 Movie는 Screening과 메시지를 통해서만 다형적으로 협력하기 때문에 변경에 대해서 영향을 주지 않을 수 있다. 변경 보호 패턴을 이용해 타입의 종류를 안정적인 인터페이스 뒤로 숨겨서 캡슐화할 수 있다.

이 과정에서 Movie는 추상 클래스가 되고 새로운 할인 정책에 대해서 추가되면 해당 클래스를 상속받아서 메시지를 전달받는 메소드만 정확하게 구현하면 된다.

도메인의 구조가 코드의 구조를 이끈다.

우리는 요구사항, 혹은 도메인에 대한 생각을 통해서 설계를 진행할 때 자연스럽게 유연성을 이끌었다.

구현을 가이드할 수 있ㄴ느 도메인 모델을 선택함으로 서 자연스럽게 코드 구조가 자연스럽고 바람직해진다.

변경과 유연성

설계를 주도하는 것은 변경. 개발자는 1. 코드를 쉽게 짜고 간단하게 설계 혹은 2. 코드를 수정하지 않고 변경을 수용할 수 있도록 함.

새로운 요구사항으로 인해서 Movie가 런타임 중에 동작하기 위해서는 큰 문제가 있다. 이런 경우을 대비해서 사용할 수 있는 것은 상속이 아닌 합성을 통해서 가능하다.

그것을 통해서 Movie는 어떤 변경도 없이 정책 변경에 대해서 어떠한 문제도 없다.

책임 주도 설계의 대안

처음부터 책임과 관련해서 설계하기가 쉽지 않다.

처음부터 잘하지 못하기에 일단 실행하는 코드를 만든 후 책임들을 올바른 위치로 이동시키는 방법이 있다.

메소드 응집도

엄청 큰 메소드는 보기 좋지 않을 뿐더라 단점이 매우 많다.

긴 메소드는 일단 분해를 해야된다. 분해할 때 클래스 단위가 아닌 메소드로 응집도 있도록 만드는 것이 중요하다.

응집도 높은 메소드를 만듬으로서 변경 가능한 설계가 되도록 만들 수 있다.

객체를 자율적으로 만들자

어떤 메서드를 어떤 클래스로 이동시킬까?

자신이 소유하고 있는 데이터를 자기 스스로 처리하도록 만드는 것이 자율적인 객체를 만드는 지름이다.

Leave a comment