
하지만 객체에게 어떤 책임을 할당할지를 결정하는 것은 쉽지 않다! 왜냐하면 책임 할당 과정은 일종의 트레이드오프 활동이기 때문이다.
즉, 동일한 문제를 해결할 수 있는 다양한 방법이 존재하며, 어떤 방법이 최선인지는 상황과 문맥에 따라 달라진다.
1️⃣ 시스템이 사용자에게 제공해야 하는 기능인 시스템 책임을 파악
2️⃣ 시스템 책임을 더 작은 책임으로 분할
3️⃣ 분할된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아 책임 할당
4️⃣ 객체가 책임을 수행하는 도중 다른 객체의 도움이 필요한 경우(스스로 처리할 수 없는 경우) 이를 책임질 적절한 객체 또는 역할 탐색
5️⃣ 해당 객체 또는 역할에게 책임을 할당함으로써 두 객체가 협력
✔️ 정보 전문가 패턴
: 책임을 수행하는 데 필요한 정보를 가지고 있는 객체에게 할당하라 (책임 할당의 기본적인 원리)
❕이때, 책임을 수행하는 객체가 정보를 '알고'있다고 해서 그 정보를 '저장'하고 있을 필요 ❌
✔️ 낮은 결합도 패턴
: 설계의 전체적인 결합도가 낮게 유지되도록 책임을 할당하라 (의존성↓ 변화에 영향↓ 재사용성↑)
✔️ 높은 응집도 패턴
: 높은 응집도를 유지할 수 있게 책임을 할당하라 (복잡성을 관리할 수 있는 수준으로 유지)
✔️ 창조자 패턴
: 아래의 조건을 최대한 많이 만족하는 B에게 A를 생성할 책임을 할당하라
public class Movie {
private MovieType movieType;
private Money discountAmount;
private double discountPercent;
// ...
private Money calculateDiscountAmount() {
switch(movieType) {
case AMOUNT_DISCOUNT:
return calculateAmountDiscountAmount();
case PERSENT_DISCOUNT:
return calculatePercentDiscountAmount();
case NONE_DISCOUNT:
return calculateNoneDiscountAmount();
}
throw new IllegalStateException();
}
}
이 메서드를 가지고 있는 클래스(DicountCondition)은 여러 이유로 변경될 수 있다 (캡슐화❌)
1️⃣ 새로운 할인이 추가되는 경우
2️⃣ 금액 할인의 로직이 변경되는 경우
3️⃣ 퍼센트 할인의 로직이 변경되는 경우
낮은 응집도를 초래하는 문제를 해결하기 위해서는 변경의 이유에 따라 클래스를 분리해야 한다.
1️⃣ 타입 분리하기
public abstract class Movie {
// ...
abstract protected Money calculateDiscountAmount();
protected Money getFee() {
return fee;
}
}
public AmountDiscountMovie extends Movie {
private double percent;
@Override
public Money calculateDiscountAmount() {
return discountAmount;
}
}
public PercentDiscountMovie extends Movie {
private double percent;
@Override
public Money calculateDiscountAmount() {
return getFee().times(percent);
}
}
public NoneDiscountMovie extends Movie {
@Override
public Money calculateDiscountAmount() {
return Money.ZERO;
}
}
❗️ 할인 정책 구현을 위해 상속을 이용하고 있기 때문에 실행 중에 할인 정책을 변경하기 어렵다!
❗️ 변경하기 위해서는 새로운 인스턴스를 생성한 후 필요한 정보를 복사해야 한다
→ 변경 전후의 인스턴스가 개념적으로는 동일한 객체를 가리키지만 물리적으로 서로 다른 객체이기 때문에 식별자의 관점에서 혼란스러울 수 있다
2️⃣ 상속 대신 합성을 사용하자
public class Movie {
private DiscountPolicy discountPolicy;
// ...
}
public interface DiscountPolicy {
Money calculateDiscountAmount();
}
✔️ 다형성 패턴
: 객체의 타입에 따라 변하는 로직이 있을 때, 타입을 명시적으로 정의하고 각 타입에 다형적으로 행동하는 책임을 할당하라!
프로그램을 if ~ else 또는 switch ~ case 등의 조건 논리를 사용해서 설계한다면 새로운 변화가 일어난 경우 조건 논리를 수정해야 한다! 이것은 프로그램을 수정하기 어렵고 변경에 취약하게 만든다!
✔️ 변경 보호 패턴
: 설계에서 변하는 것이 무엇인지 고려하고 변하는 개념을 캡슐화하라
: 책임 관점에서 사고하기 위해서는 충분한 경험과 학습이 필요하다. 따라서 최대한 빠르게 목적한 기능을 수행하는 코드를 작성하고, 코드를 얻고 난 후 코드 상에 명확하게 드러나는 책임들을 올바른 위치로 이동시키자!
✔️ 리팩터링
: 이해하기 쉽고 수정하기 쉬운 소프트웨어로 개선하기 위해 겉으로 보이는 동작은 바꾸지 않은 채 내부 구조를 변경하는 것
1️⃣ 메서드를 작게 분해해서 각 메서드의 응집도를 높여라
❗️ 작은 메서드는 실제로 이름을 잘 지었을 때만 그 진가가 드러나므로, 이름을 지을 때 주의해야 한다
2️⃣ 메서드가 사용하는 데이터를 저장하고 있는 클래스로 메서드를 이동시키자