오브젝트 Ch.5

Manx·2022년 5월 17일
0
post-thumbnail

'오브젝트: 코드로 이해하는 객체지향 설계' 1주차
분량 : 1장 ~ 5장
기간 : 22.5.7 ~ 22.5.14

Ch.5 책임 할당하기

내가 생각하는 핵심 내용 : 코드를 통해 변경의 이유를 파악하는 방법

객체지향 설계의 핵심은 책임에 초점에 맞추는 것이다.
그러나, 어떤 책임을 할당할지를 결정하기가 쉽지 않다. 책임 할당 과정은 일종의 트레이드오프 활동이다.
다양한 책임 할당 방법이 존재하며, 어떤 방법이 최선인지는 상황과 문맥에 따라 달라진다.
=> 올바른 책임을 할당하기 위해서는 다양한 관점에서 설계를 평가할 수 있어야 한다.

데이터 중심 설계에서 책임 중심 설계로

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

데이터 중심의 설계
이 객체가 필요한 데이터는 무엇인가 => 데이터를 처리하는데 필요한 오퍼레이션은 무엇인가

책임 중심의 설계
이 객체가 수행해야 하는 책임은 무엇인가 => 이 책임을 수행하는데 필요한 데이터는 무엇인가

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

객체에게 할당된 책임의 품질은 협력에 적합한 정도로 결정된다.
책임은 객체의 입장이 아니라 객체가 참여하는 협력에 적합해야 한다.
협력을 시작하는 주체는 메시지 전송자이기 때문에 협력에 적합한 책임이란 메시지 수신자가 아닌 메시지 전송자에게 적합한 책임을 의미한다.

메시지를 먼저 결정하기 때문에 송신자는 수신자에 대한 어떠한 가정도 할 수 없다. (수신자의 완벽한 캡슐화)

GRASP

General Responsibility Assignment Software Pattern(일반적인 책임 할당을 위한 소프트웨어 패턴)

CREATOR (창조자 패턴)
어떤 객체는 최종 결과의 인스턴스를 생성할 책임을 가져야만 한다.

어떤 객체 B에게 A 객체 생성 책임을 할당해야 할 경우

  • B가 A 객체를 포함하거나 참조한다.
  • B가 A 객체를 기록한다.
  • B가 A 객체를 긴밀하게 사용한다.
  • B가 A 객체를 초기화하는 데 필요한 데이터를 가지고 있다.

이미 결합돼 있는 객체에게 생성 책임을 할당하는 것은 설계의 전체적인 결합도에 영향을 미치지 않는다.
=> 이미 존재하는 객체 사이의 관계를 이용하기 때문에 낮은 결합도를 유지할 수 있게 한다.

코드를 통해 변경의 이유를 파악할 수 있는 방법

public class DiscountCondition {
    private DiscountConditionType type;
    private int sequence;
    private DayOfWeek dayOfWeek;
    private LocalTime startTime;
    private LocalTime endTime;

    public boolean isSatisfiedBy(Screening screening) {
        if (type == DiscountConditionType.PERIOD) {
            return isSatisfiedByPeriod(screening);
        }

        return isSatisfiedBySequence(screening);
    }

    private boolean isSatisfiedByPeriod(Screening screening) {
        return dayOfWeek.equals(screening.getWhenScreened().getDayOfWeek()) &&
                startTime.compareTo(screening.getWhenScreened().toLocalTime()) <= 0 &&
                endTime.compareTo(screening.getWhenScreened().toLocalTime()) <= 0;
    }

    private boolean isSatisfiedBySequence(Screening screening) {
        return sequence == screening.getSequence();
    }

1. 인스턴스 변수가 초기화되는 시점을 살펴본다.

DiscountCondition이 순번 조건을 표현하는 경우 isSatisfiedBySequence 메소드만 사용하면 되기 때문에 squence는 초기화되지만 dayOfWeek, startTime, endTime은 초기화 되지 않는다. 반대로 DiscountCondition이 기간 조건을 표현하는 경우에는 dayOfWeek, startTime, endTime은 초기화되지만 sequence는 초기화되지 않는다. 클래스의 속성이 서로 다른 시점에 초기화되거나 일부만 초기화된다는 것은 응집도가 낮다는 증거다.
따라서 함께 초기화되는 속성을 기준으로 코드를 분리해야 한다.

2. 메서드들이 인스턴스 변수를 사용하는 방식을 살펴본다.

모든 메소드가 객체의 모든 속성을 사용한다면 클래스의 응집도는 높다고 볼 수 있다. 반면 메서드들이 사용하는 속성에 따라 그룹이 나뉜다면 클래스의 응집도가 낮다고 볼 수 있다. isSatisfiedBySequence 메소드와 isSatisfiedByPeriod 메서드가 이 경우에 해당한다.
속성 그룹과 해당 그룹에 접근하는 메서드 그룹을 기준으로 코드를 분리해야 한다.


다형성을 통해 분리하기

SequenceCondition과 PeriodCondition 둘 모두 할인 여부를 판단하는 동일한 책임을 수행하고 있을 뿐이다.

둘에게 역할의 개념을 적용하면 구체적인 클래스는 알지 못한 채 오직 역할에 대해서만 결합되도록 의존성을 제한할 수 있다.

public interface DiscountCondition {
	boolean isSatisfiedBy(Screening screening)
}

public class PeriodCondition implements DiscountCondition {...}

public class SequenceCondition implements DiscountCondition {...}

이제 DiscountCondition을 이용하는 객체는 협력하는 객체의 구체적인 타입을 몰라도 된다. 협력하는 객체가 DiscountCondition 역할을 수행할 수 있고 isSatisfiedBy 메시지를 이해할 수 있다는 사실만 알고 있으면 충분하다.

profile
백엔드 개발자

0개의 댓글