조영호 님의 "오브젝트" 책을 정리한 글입니다.
1. 개방-폐쇄 원칙
컴파일타임 의존성을 고정시키고 런타임 의존성을 변경하라
개방-폐쇄 원칙
- 기존의 코드를 수정하지 않고도 애플리케이션의 동작을 확장할 수 있는 설계
- 컴파일타임 의존성은 유지하면서 런타임 의존성의 가능성을 확장하고 수정할 수 있는 구조
추상화가 핵심이다
항목 | 내용 |
핵심 |
추상화에 의존하는 것
|
확장 방식 |
상속을 통해 생략된 부분을 구체화
|
확장의 기반 |
추상화는 생략된 부분을 통해 확장의 여지를 남김
|
폐쇄를 가능하게 하는 요소 |
의존성의 방향
|
의존성 방향이 향해야 할 곳 |
구현이 아닌 추상화 계층
|
public abstract class DiscountPolicy {
private List<DiscountCondition> conditions = new ArrayList<>();
public DefaultDiscountPolicy(DiscountCondition ... conditions) {
this.conditions = Arrays.asList(conditions);
}
public Money calculateDiscountAmount(Screening screening) {
for (DiscountCondition each : conditions) {
if (each.isSatisfiedBy(screening)) {
return getDiscountAmount(screening);
}
}
return screening.getMovieFee();
}
abstract protected Money getDiscountAmount(Screening screening);
}
- 변하지 않는 부분: 할인 여부를 판단하는 로직
- 변하는 부분: 할인된 요금을 계산하는 방법
2. 생성 사용 분리
개방-폐쇄원칙을 따르는 설계를 하기 어려운 경우
- 알아야 하는 지식이 많아야 할 경우
- 한번에 생성과 사용이란 두가지 책임을 가질 경우
해결 방법
항목 | 설명 |
대안 | 생성 책임을 클라이언트에게 옮기기 (클라이언트는 객체 생성 책임을 가지므로 특정 컨텍스트에 묶여도 무방) |
문제점 | - 객체 생성에 대한 구체적인 지식이 클라이언트에 노출됨 - 클라이언트가 구현에에 의존하게 됨 (결합도 증가) - 전달할 인자가 많아지면 객체 생성 실수가 일어날 수 있음 |
FACTORY 추가하기
FACTORY 패턴
- 생성 책임 모두를 FACTORY에 옮기기
- 클라이언트는 사용과 관련된 책임만 지고 어떤 관련지식도 가지지 않을 수 있음
더보기
public class Factory {
public Movie createAvatarMovie() {
return new Movie(
"아바타",
Duration.ofMinutes(120),
Money.wons(10000),
new AmountDiscountPolicy(new Money(new BigDecimal(1000)), new DiscountCondition())
);
}
}
public class Client {
private Factory factory;
public Client(Factory factory) {
this.factory = factory;
}
public Money getAvatarFee() {
Movie avatar = factory.createAvatarMovie();
return avatar.getFee();
}
}
순수한 가공물에게 책임 할당하기
개념 | 설명 |
표현적 분해 | 도메인 모델을 구성하는 개념 중 적절한 후보 객체를 찾아 그들에게 책임을 위임함 |
행위적 분해 (순수한 가공물에게 책임 할당) |
도메인 객체가 없거나 어울리지 않는 경우, 도메인과 무관한 인공적 객체에게 책임을 할당함
(ex: Helper, Policy, Strategy 등) |
- 도메인 추상화를 기반으로 부족한 점은 컴퓨터 내의 인공적인 객체를 이용해 보충하여 시스템을 설계하기
- 우리의 목표는 요구사항을 효과적으로 응답할 수 있는 좋은 설계를 만드는 것
- 현실세계를 그대로 모방하는 것이 아님
- 도메인 개념만 고집할 경우 오히려 낮은 응집도, 높은 결합도, 낮은 재사용성 문제가 발생할 수 있음
3. 의존성 주입
숨겨진 의존성은 나쁘다
- 의존성 해결 방법은 두가지로 나뉨
의존성 주입
- 사용하는 객체가 아닌 외부의 독립적인 객체가 인스턴스를 생성한 후 이를 전달하는 방법
- 생성자 주입 / setter 주입 / 메서드 주입
service locator
- 의존성을 해결할 객체들을 보관하는 일종의 저장소
- 객체가 직접 service locator에게 의존성을 요청함
항목 | 설명 |
단점 |
- 의존성을 감춤: 코드상에서 어떤 의존성을 사용하는지 명확히 보이지 않음
- 디버깅 어려움: 의존성 해결 문제가 컴파일 타임이 아닌 런타임에 발생함 - 테스트 어려움: Locator는 전역 상태 공유 → 테스트 간 고립이 어려움 (테스트 독립성 위반) |
원인 | - 숨겨진 의존성: 클래스 내부에서 의존 객체를 요청하므로, 외부에서는 어떤 의존성이 필요한지 알기 어려움 (캡슐화 위반) |
더보기
public class Movie {
private String title;
private Duration runningTime;
private Money fee;
private DiscountPolicy discountPolicy;
private MovieType movieType;
private Money discountAmount;
private double discountPercent;
public Movie(String title, Duration runningTime, Money fee) {
this.title = title;
this.runningTime = runningTime;
this.fee = fee;
this.discountPolicy = ServiceLocator.discountPolicy();
}
}
public class ServiceLocator {
private static ServiceLocator soleInstance = new ServiceLocator();
private DiscountPolicy discountPolicy;
public static DiscountPolicy discountPolicy() {
return soleInstance.discountPolicy;
}
public static void provide(DiscountPolicy discountPolicy) {
soleInstance.discountPolicy = discountPolicy;
}
private ServiceLocator() {}
}
ServiceLocator.provide(new AmountDiscountPolicy(...));
Movie avatar = new Movie(
"아바타",
Duration.ofMinutes(120),
Money.wons(10000)
);
4. 의존성 역전 원칙
추상화와 의존성 역전
- 의존성은 변경의 전파와 관련된 것
- 설계는 변경의 영향을 최소화하도록 의존성을 관리해야 함
의존성 역전 법칙
항목 | 설명 |
정의 | 구체적인 사항이 추상화에 의존해야 함. (하위 모듈이 상위 모듈을 의존하게 만들어야 함) |
핵심 설계 원칙 | 추상화에 의존해야 함 (구현 클래스 X) |
장점 | - 변경 용이: 하위 수준의 변경으로 인해 상위 수준이 영향 받는 것을 방지 - 재사용성: 상위 수준 모듈을 다양한 컨텍스트에서 재사용 가능 |
의존성 역전 원칙과 패키지
- 인터페이스의 소유권도 역전이 해당됨
SEPARATED INTERFACE 패턴
항목 | 설명 |
정의 | 추상화와 구현체를 서로 다른 패키지에 분리해서 배치하는 설계 패턴 |
구현 방식 | 1. 인터페이스의 소유권을 클라이언트측으로 옮기기 2. 함께 재사용될 필요가 없는 클래스들은 별도의 독립적인 패키지에 모으기 |
장점 | - 재사용성: 재사용하기 위해서 단지 구현체 패키지만 import하면 됨 - 컴파일 시간 단축: 새로운 구현체 추가 시, 구현체 패키지만 컴파일해서 배포하면 됨 |
Client: Movie, DiscountPolicy
Server: AmountDiscountPolicy, PercentDiscountPolicy
5. 유연성에 대한 조언
유연한 설계는 유연성이 필요할 때만 옳다
- 설계가 유연할수록 클래스 구조와 객체 구조 사이의 거리는 점점 멀어짐
협력과 책임이 중요하다
'Code > OOP' 카테고리의 다른 글
[오브젝트] 8. 의존성 관리하기 (0) | 2025.04.04 |
---|---|
[오브젝트] 6. 메시지와 인터페이스 (0) | 2025.04.04 |
[오브젝트] 5. 책임 할당하기 (0) | 2025.04.03 |
[오브젝트] 4. 설계 품질과 트레이드오프 (0) | 2025.04.03 |
[오브젝트] 3. 역할, 책임, 협력 (0) | 2025.04.03 |