본 글은 인프런 김영한님의 스프링 완전 정복 로드맵을 기반으로 정리했습니다.
클래스A가 동작하기 위해서 클래스B의 기능을 사용한다. 이 때, 클래스A는 클래스B에 의존한다, 혹은 의존성을 가진다고 표현한다.
public class OrderService {
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
public Long processOrder() {
// 주문 처리 로직
}
}
RateDiscountPolicy는 DiscountPolicy를 구현한 구체 클래스다. 이름으로 미루어보아 비율 할인을 적용한다는 것을 유추할 수 있다. 위처럼 자신이 의존하는 객체를 new
키워드로 직접 생성하는 방식의 문제는 무엇일까?
OrderService의 동작만 테스트하고 싶은데 RateDiscountPolicy에 직접 의존하기 때문에 OrderService만 따로 테스트하기가 어려워진다.
SRP란 클래스는 하나의 책임(Single Responsibility)만을 가져야 한다는 것이다.
클래스가 두 가지 다른 이유로 인해 변경되어야 할 때 그 클래스는 SRP를 위배한다. 책임이 하나보다 많기 때문에 다른 이유로 변경되어야 하는 것이다.
OrderService 클래스는 주문을 처리하는 책임, 어떤 할인 정책을 적용할 것인지 선택하는 두 가지 책임을 가진다. 주문 처리 방식이 변하면 OrderService 클래스의 로직을 고쳐야한다. 할인 정책이 변했을 때도 OrderService 클래스를 고쳐야 한다.
OCP란 변경에는 닫혀있고(closed for modification) 확장에는 열려있어야 (open for extension) 한다는 것이다.
엔티티(클래스, 모듈, 함수)는 자신의 코드를 고치지 않고도 기능을 확장할 수 있어야한다.
고정할인 정책을 추가하기 위해 DiscountPolicy 를 구현한 FixDiscountPolicy 클래스를 추가로 만들었다. 다형성을 통해 할인 정책을 확장한 것이다. 그러면 OrderService의 할인 정책을 변경하려면 어떻게 해야할까? OrderService 클래스의 소스코드를 고쳐야한다.
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
추상화와 다형성을 통해 기능 확장에열려 있긴 하지만 기능 변경에 닫혀있지 않은 것이다.
DIP란 고수준 모듈이 저수준 모듈에 직접 의존하면 안 된다는 것이다.
고수준 모듈, 저수준 모듈 모두 추상화에 의존하도록 의존성을 역전(Dependency Inversion)시켜야 한다.
고수준 모듈은 OrderService다. 저수준 모듈은 RateDiscountPolicy다. 주문을 처리하는 책임은 특정한 할인 정책을 적용하는 책임에 비해 추상화 수준이 높기 때문이다. 그러나, 위의 코드에서 OrderService는 어떤 할인정책을 적용할 것인지에 대한 세부사항을 직접 선택하고 있다. 더 좋은 설계는 추상화에 의존하도록 의존성을 역전하는 것이다.
public class OrderService {
private final DiscountPolicy discountPolicy;
public OrderService(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
public Long processOrder() {
// 주문 처리 로직
}
}
생성자를 통해 의존성을 주입받도록 바꿔보았다. 단순한 변화지만 위에서 언급한 모든 문제가 해결된다.
유닛 테스트가 용이해진다. DiscountPolicy를 모의 객체로 주입받으면 OrderService만 따로 테스트하기가 더 쉬워진다.
OrderService는 주문을 처리하는 책임만 가진다. 특정한 DiscountPolicy를 선택하는 책임이 사라진다. (SRP)
DiscountPolicy를 외부에서 주입받기 때문에 할인 정책이 변경되도 OrderService클래스의 소스코드는 변경되지 않는다. 변경에 닫혀있도록 변경된 것이다. (OCP)
고수준 모듈 OrderService는 추상화(DiscountPolicy)에만 의존한다. 더 이상 저수준 세부사항 RateDiscountPolicy, FixDiscountPolicy 에 의존하지 않는다. (DIP)
스프링에서는 IoC 컨테이너가 의존관계를 자동으로 주입해준다. 물론 수동으로 주입할 수도 있다. DI 컨테이너는 의존관계 자동 주입외에도 빈 객체 싱글톤 관리, 라이프사이클 관리 등 다양한 기능을 제공하는 스프링 프레임워크의 핵심이다. 앞으로 이어질 글들에서 더 자세히 다루도록 하겠다.