개방 폐쇄 원칙은 “확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다” 다시말해, “객체의 기능을 확장할 수 있으면서 기존의 코드(클라이언트)는 수정하지 않는다”
런타임 의존성과 컴파일타임 의존성
OCP는 런타임 의존성과 컴파일타임 의존성에 관한 이야기다.
- Movie는 DiscountPolicy(구현체가 아닌 인터페이스나 부모클래스)에 의존하기 떄문에 자식 클래스로 OverlappedDiscountPolicy 클래스를 추가하더라도 기존의 코드는 변하지 않는다.
- 변경의 이유가 1가지이다. 그건 바로 서비스 계층의 할인 정책 인터페이스
추상화
OCP의 핵심은 추상화에 의존하는 것이다. ’폐쇄’를 가능하게 하는 것은 추상화에 대한 의존성의 방향이다.
public class Movie {
//...
private DiscountPolicy discountPolicy;
public Movie(String title, Duration runningTime, Money fee) {
// ...
this.discountPolicy = new AmountDiscountPolicy();
}
// ...
}
- 위 코드를 보면, new AmountDiscountPolicy()를 주입받고 있는데, 이렇게 되면 인터페이스의 구현체를 의존하게 되고 OCP를 위반하는 코드라고 볼 수 있다.
- OCP는 기존 코드를 변경하지 않고 확장할 수 있어야 한다는 점 때문이다.
- 개선된 코드
public class Movie {
private DiscountPolicy discountPolicy;
public Movie(tring title, Duration runningTime, Money fee, DiscountPolicy discountPolicy) {
// ..
this.discountPolicy = discountPolicy;
}
// ..
}
- 인터페이스에만 의존하도록 하고, 구체 정책은 외부에서 결정하여 전달받는 방식으로 변경한다.
- OCP를 준수
- 가장 보편적인 방법은 객체 생성의 책임을 클라이언트로 옮기는 것이다.
Factory
객체에 대한 생성과 사용을 분리하기 위해 객체 생성에 특화된 객체
// DiscountPolicyFactory.java
public class DiscountPolicyFactory {
public DiscountPolicy createDiscountPolicy(String policyType) {
// policyType 등에 따라 적절한 구현체 생성
if (policyType.equals("AMOUNT")) {
return new AmountDiscountPolicy();
} else if (policyType.equals("PERCENT")) {
return new PercentDiscountPolicy();
}
// 확장 가능
return null;
}
}
// Movie.java
public class Movie {
private DiscountPolicy discountPolicy;
// Factory를 통해 주입받는다.
// 어떤 DiscountPolicy를 쓸지는 Factory가 결정
public Movie(String title, Duration runningTime, Money fee,
DiscountPolicyFactory factory, String policyType) {
// ...
this.discountPolicy = factory.createDiscountPolicy(policyType);
}
// ...
}
// Client.java
public class Client {
public static void main(String[] args) {
DiscountPolicyFactory factory = new DiscountPolicyFactory();
// 어떤 정책을 쓸지는 클라이언트가 결정하되,
// 실제 인스턴스 생성은 factory가 담당
Movie movie = new Movie("Avengers", Duration.ofMinutes(120), new Money(12000),
factory, "AMOUNT");
// ...
}
}
DI
생성과 사용의 책임을 분리한 후에 사용되는 방법이 DI이다.
DIP
자주 변하는 구체적인 클래스보다는 변하기 어려운 인터페이스나 상위 클래스와 의존 관계를 맺으라는 원칙이 애플리케이션을 변경에 용이하게 할 수 있다.
상위 모듈은 하위 모듈에 의존해서는 안된다. 추상화에 의존하라. 객체는 세부 사항에 의존해서는 안된다. 구체적인 사항은 추상화에 의존해야 한다.
유연한 설계는 곧 복잡한 설계
유연한 설계는 코드를 복잡하게 만들고 가독성을 해친다.
유연성이 필요한 때에만 유연한 설계를 하자.
'아키텍처' 카테고리의 다른 글
[클린 아키텍처] 아키텍처 요소 테스트 전략 & 경계 간 매핑하기 (0) | 2025.03.28 |
---|---|
[클린 아키텍처] 웹 어댑터 구현하기 & 영속성 어댑터 구현하기 (0) | 2025.03.28 |
[클린 아키텍처] 유스케이스 (0) | 2025.03.27 |
[클린 아키텍처] 의존성 역전하기 (0) | 2025.03.23 |
[클린 아키텍처] 계층형 아키텍처(Layered Architecture)의 문제는 무엇일까? (0) | 2025.03.23 |