애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스를 만들자.
앞서 우리는 프로젝트 코드가 OCP, DIP를 위반한다는 것을 알았다.
새로운 할인 정책을 적용하려면 '클라이언트 코드'인 주문 서비스 구현체도 함께 변경해야 했다. 인터페이스뿐만 아니라, 구체 클래스도 함께 의존하고 있었던 것이다.
OrderServiceImpl
은 DiscountPolicy
의 인터페이스 뿐만 아니라 구체 클래스도 함께 의존한다. 인터페이스에만 의존하도록 OrderServiceImpl
코드를 변경해보자.
public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
}
해당 코드로 변경하여 실제 실행을 해보면 NPE(null pointer exception)가 발생한다.
이 문제를 해결하려면 누군가가 클라이언트인 OrderServiceImpl 에 DiscountPolicy 의 구현 객체를 대신 생성하고 주입해주어야 한다.
바로 AppConfig
이다.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
1. AppConfig는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.
- MemoryMemberRepository
- OrderServiceImpl
- FixDiscountPolicy
2. AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결)해준다.
- MemberServiceImpl ▶ MemoryMemberRepository
- OrderServiceImpl ▶ MemoryMemberRepository , FixDiscountPolicy
AppConfig를 통해서 생성자를 통해 주입할 것이므로, MemberServiceImpl
과 OrderserviceImpl
에 생성자를 만들어 주어야 한다.
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
OrderServiceImpl
도 마찬가지로 생성자를 만들어 주어야 한다.
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
설계 변경으로 이제 더이상 MemberServiceImpl
은 MemoryMemberRepository
를 의존하지 않고, OrderServiceImpl
은 FixDiscountPolicy
를 의존하지 않는다.
단지, 인터페이스만 의존할 뿐이다.
MemberServiceImpl 입장에서 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다. 예를 들어, MemberServiceImpl 의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부( AppConfig )에서 결정된다. MemberServiceImpl 은 이제부터 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중하면 된다.
AppConfig의 등장으로 애플리케이션이 크게 사용 영역과, 객체를 생성하고 구성(Configuration)하는 영역으로 분리됐다. 할인 정책을 변경해도 AppConfig가 있는 구성 영역만 변경하면 되고, 사용 영역은 변경할 필요가 없어진 것이다. 물론 클라이언트 코드인 주문 서비스 코드도 변경하지 않는다.
AppConfig의 등장으로 애플리케이션이 크게 사용 영역과, 객체를 생성하고 구성(Configuration)하는 영역으로 분리되었다. 구성의 분리를 통해 사용영역의 코드는 전혀 손 댈 필요가 없다.
할인 정책을 FixDiscountPolicy에서 RateDiscountPolicy로 변경해도 구성 영역만 영향을 받고, 사용 영역은 전혀 영향을 받지 않는다.
AppConfig에서 FixDiscountPolicy를 RateDiscountPolicy로 변경했다.
20000원의 10%인 2000원이 할인된 결과가 나왔다.
즉, 할인 정책을 변경해도, 애플리케이션의 구성 역할을 담당하는 AppConfig만 변경하면 되는 것이다. OrderServiceImpl 를 포함해서 사용 영역의 어떤 코드도 변경할 필요가 없다.
프로젝트는 이제 OCP, DIP를 준수한다.
Ref.
스프링 핵심원리(김영한)