public class OrderServiceImpl implements OrderService {
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
DIP: 주문서비스 클라이언트 OrderServiceImpl
은 DiscountPolicy
인터페이스에 의존하면서 DIP를 지킨 것 같지만, 클래스 의존관계를 분석해 보면 추상(인터페이스) 뿐만 아니라 구체(구현) 클래스에도 의존하고 있다.
추상(인터페이스) 의존: DiscountPolicy
구체(구현) 클래스: FixDiscountPolicy
,RateDiscountPolicy
OCP: 현재 코드는 기능을 확장해서 변경하면, 클라이언트 코드에 영향을 준다. 따라서 OCP를 위반한다.
public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
}
구현체가 없기 때문에 그냥 실행할 경우 NPE(null pointer exception)가 발생한다.
이 문제를 해결하려면 누군가가 클라이언트인 OrderServiceImpl
에 DiscountPolicy
의 구현 객체를 대신 생성하고 주입해주어야 한다. -> AppConfig의 등장
AppConfig: 애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(),new FixDiscountPolicy());
}
}
AppConfig는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.
MemberServiceImpl
MemoryMemberRepository
OrderServiceImpl
FixDiscountPolicy
AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결)해준다.
MemberServiceImpl
-> MemoryMemberRepository
OrderServiceImpl
-> MemoryMemberRepository
, FixDiscountPolic
객체의 생성과 연결은 AppConfig
가 담당한다.
DIP 완성: MemberServiceImpl
은 MemberRepository
인 추상에만 의존하면 된다. 이제 구체 클래스를 몰라도 된다.
관심사의 분리: 객체를 생성하고 연결하는 역할과 실행하는 역할이 명확히 분리되었다.
appConfig
객체는 memoryMemberRepository
객체를 생성하고 그 참조값을 memberServiceImpl 을 생성하면서 생성자로 전달한다.
클라이언트인 memberServiceImpl
입장에서 보면 의존관계를 마치 외부에서 주입해주는 것 같다고 해서 DI(Dependency Injection) 우리말로 의존관계 주입 또는 의존성 주입이라 한다.
현재 AppConfig를 보면 중복이 있고, 역할에 따른 구현이 잘 안보인다.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(),newFixDiscountPolicy());
}
}
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(
memberRepository(),
discountPolicy());
}
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
}
new MemoryMemberRepository()
이 부분이 중복 제거되었다. 이제 MemoryMemberRepository
를 다른 구현체로 변경할 때 한 부분만 변경하면 된다.
AppConfig
를 보면 역할과 구현 클래스가 한눈에 들어온다. 애플리케이션 전체 구성이 어떻게 되어있는지 빠르게 파악할 수 있다.
이제 할인 정책을 변경해도, 애플리케이션의 구성 역할을 담당하는 AppComfig만 변경하면 된다. 클라이언트 코드인 OrderServiceImpl
를 포함해서 사용 영역의 어떤 코드도 변경할 필요가 없다.
구성 영역은 당연히 변경된다. 구성 역할을 담당하는 AppConfig를 애플리케이션이라는 공연의 기획자로 생각하자. 공연 기획자는 공연 참여자인 구현 객체들을 모두 알아야 한다.
FixDiscountPolicy
-> RateDiscountPolicy
로 변경해도 구성 영역만 영향을 받고, 사용 영역은 전혀 영향을 받지 않는다.