Ch.9 유연한 설계
개방-폐쇄 원칙 (Open-Closed Principle, OCP)
OCP - 소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다.
확장에 대해 열려 있다 - 요구사항이 변경되면 이 변경에 맞게 새로운 '동작'을 추가하기 쉽다.
수정에 대해 닫혀 있다 - 기존의 '코드'를 수정하지 않고도 애플리케이션의 '동작'을 추가하거나 변경할 수 있다.
핵심은 추상화에 의존하는 것이다.
컴파일타임 의존성, 런타임 의존성
예제에서...
컴파일타임 의존성 -
Movie
는 추상 클래스인DiscountPolicy
에 의존한다.런타임 의존성 -
Movie
는DiscountPolicy
를 구현한AmountDiscountPolicy
,PercentDiscountPolicy
에 의존한다.
OCP을 잘 따르는 코드는 컴파일타임 의존성을 수정하지 않고도 런타임 의존성을 쉽게 변경할 수 있다.
예제에서 중복 할인 정책을 구현하는
OverlappedDiscountPolicy
클래스를 추가로 구현하더라도Movie
클래스는 여전히DiscountPolicy
클래스에만 의존할 수 있다.
주의점 & 참고사항
OCP적인(?) 설계는 공짜가 아니다. 단순히 추상화를 했다고 해서 수정에 대해 닫힌 설계가 되는 건 아니다.
변하는 것과 변하지 않는 것을 구분하고 추상화의 목적으로 삼아야 한다.
생성과 사용을 분리하자. (Separate use from creation)
생성자 외부에서 생성해 생성자로 전달하자.
추상화에만 의존시키기 위해서 생성자 내부에서 new 키워드로 구체화된 인스턴스를 만들지 말자.
생성에 특화된 FACTORY를 추가할 수도 있다.
PURE FABRICATION
모든 책임을 도메인 객체에게 할당하면 낮은 응집도, 높은 결합도, 재사용성 저하와 같은 문제에 봉착할 수 있다.
그럴 땐, 주저하지 말고 도메인 개념을 표현한 객체가 아닌, 설계자가 편의를 위해 임의로 만들어낸 가공의 객체인 PURE FABRICATION (순수한 가공물)에 책임을 할당하자. (편의상 만들어낸 클래스)
Dependency Injection (의존성 주입)
외부에 독립적인 객체가 인스턴스를 생성한 후 이를 전달해 의존성을 해결하는 방법.
앞에 장에서 여러 번 설명했듯, 세 가지 방법이 있다.
생성자 주입 (Constructor Injection)
Setter 주입 (Setter Injection)
메서드 주입 (Method Injection)
(Bonus) 인터페이스 주입 (Interface Injection) (e.g., DiscountPolicyInjectable)
메서드 주입은 메서드 호출 주입 (Method Call Injection)이라고도 부른다. 메서드 실행 시 인자를 이용해 의존성을 해결하며, 메서드가 의존성을 필요로 하는 유일한 경우일 때 사용할 수 있다.
Service Locator
클래스를 만들어 static
키워드를 이용해 singleton
패턴을 구현한다.
스프링의 컨테이너 / @Configuration
클래스와는 완전히 다르다.
@Configuration
,@Bean
숨겨진 의존성을 그냥 전역 클래스로 import해서 주입하기 때문.
public Move(...) {
// ...
this.discountPolicy = ServiceLocator.discountPolicy();
}
숨겨진 의존성
의존성을 숨기는 코드는 단위 테스트 작성이 어렵다.
Service Locator는 정적 변수로 객체를 관리하므로 모든 단위 테스트가 상태를 공유하게 된다. 그래서 테스트가 고립되어야 한다는 단위 테스트의 기본 원칙을 위반한다.
숨겨진 의존성은 의존성을 이해하기 위해 코드의 내부 구현을 이해할 것을 강요한다.
따라서 숨겨진 의존성은 캡슐화를 위반한다.
숨겨진 의존성은 의존성의 대상을 설정하는 시점, 해결하는 시점을 멀리 떨어뜨려 놔서 코드를 이해하고 디버깅하기 어렵게 만든다.
결론은... SERVICE LOCATOR 패턴을 떠나서 숨겨진 의존성보다 명시적 의존성이 좋다는 것!
의존성 역전
유연하고 재사용 가능한 설계를 위해 모든 의존성을 추상 클래스, 인터페이스와 같은 추상화를 향하도록 하자.
여기서 나오는 개념이 의존성 역전 원칙 (Dependency Inversion Principle, DIP)이다.
상위 수준의 모듈은 하위 수준의 모듈에 의존해선 안 된다. 둘 모두 추상화에 의존해야 한다.
추상화는 구체적인 사항에 의존해서는 안 된다. 구체적인 사항은 추상화에 의존해야 한다.
유연성
유연성의 트레이드오프로 항상 복잡성이 올라간다.
e.g., 정적인 클래스의 구조와 실행 시점의 동적인 객체 구조가 다르다.
협력과 책임
객체의 역할과 책임이 자리를 잡기 전에 너무 성급하게 객체의 생성에 집중하지 말자.
객체를 생성할 책임을 담당할 객체나 객체 생성 메커니즘을 결정하는 시점(e.g., FACTORY)은 책임 할당의 마지막 단계로 미루자.
Last updated
Was this helpful?