FixDiscountPolicy
에서 정률할인정책 RateDiscountPolicy
으로 변경하고자 한다.public class RateDiscountPolicy implements DiscountPolicy {
private int discountPercent = 10;
@Override
public int discount(Member member, int price) {...}
}
OrderServiceImpl
의 코드를 고쳐야한다.public class OrderServiceImpl implements OrderService {
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
// 기존 코드
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
OrderServiceImpl
는 인터페이스 discountPolicy
에 의존하며 DIP를 지킨 것 같아보인다.discountPolicy
FixDiscountPolicy
, RateDiscountPolicy
OrderServiceImpl
가 인터페이스 discountPolicy
에만 의존한다고 생각했다. OrderServiceImpl
이 DiscountPolicy
인터페이스 뿐만 아니라 FixDiscountPolicy
인 구체 클래스도 함께 의존하고 있다private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
FixDiscountPolicy
를 RateDiscountPolicy
로 변경하는 순간 OrderServiceImpl
의 소스 코드도 함께 변경해야 한다OrderServiceImpl
은 DiscountPolicy
의 인터페이스 뿐만 아니라 구체 클래스도 함께 의존한다public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
}
OrderServiceImpl
에 DiscountPolicy
의 구현 객체를 대신 생성하고 주입해주어야 한다따라서 별도의 배우(구현체)를 선정해주는 기획자가 필요하다
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
MemberServiceImpl
MemoryMemberRepository
OrderServiceImpl
FixDiscountPolicy
MemberServiceImpl
MemoryMemberRepositor
OrderServiceImpl
MemoryMemberRepositor
, FixDiscountPolicy
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
// 생성자 주입
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
...
}
MemberServiceImpl
은 MemoryMemberRepository
를 의존하지 않는다.MemberRepository
인터페이스만 의존한다. 즉, MemberRepository
인터페이스를 구현한 구현체 어떤 것이 들어와도 된다.MemberServiceImpl
입장에서 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다.MemberServiceImpl
의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부(AppConfig
)에서 결정된다.MemberServiceImpl
은 이제부터 의존관계에 대한 고민은 외부에 위임하고 실행(단 하나의 책임)에만 집중하면 된다AppConfig
의 역할이다.MemberServiceImpl
은 MemberRepository
인 추상에만 의존할 뿐, 구체화에 의존하지 않는다.AppConfig
)과 실행하는 역할( MemberServiceImpl
)이 명확히 분리되었다appConfig
객체는 memoryMemberRepository
객체를 생성하고 그 참조값을 memberServiceImpl
을 생성하면서 생성자로 전달한다.memberServiceImpl
입장에서 보면 의존관계를 마치 외부에서 주입해주는 것이므로 DI(Dependency Injection) 의존관계 주입이라 한다.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;
}
...
}
OrderServiceImpl
은 MemoryMemberRepository
와 FixDiscountPolicy
를 의존하지 않는다.MemberRepository
와 DiscountPolicy
인터페이스만 의존한다. 즉, MemberRepository
, DiscountPolicy
인터페이스를 구현한 구현체 어떤 것이 들어와도 된다.OrderServiceImpl
입장에서 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다.OrderServiceImpl
의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부(AppConfig
)에서 결정된다.OrderServiceImpl
은 이제부터 의존관계에 대한 고민은 외부에 위임하고 실행(단 하나의 책임)에만 집중하면 된다.OrderServiceImpl
에는 MemoryMemberRepository
, FixDiscountPolicy
객체의 의존관계가 주입된다MemberRepository
, DiscountPolicy
etc.) = 배역MemoryMemberRepository
, FixDiscountPolicy
etc.) = 배우public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
public class AppConfig {
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
// 리펙토링 => 역할과 구현클래스가 한눈에 보임
// 어플리케이션의 전체의 구성파악에 용이하다
}
new MemoryMemberRepository()
중복을 제거되었다.MemberService
역할에 대한 구현은 MemberServiceImpl
MemberRepository
역할에 대한 구현은 MemoryMemberRepository
OrderService
역할에 대한 구현은 OrderServiceImpl
DiscountPolicy
역할에 대한 구현은 RateDiscountPolicy
FixDiscountPolicy
를 선택할 경우RateDiscountPolicy
로 변경할 경우FixDiscountPolicy
RateDiscountPolicy
로 변경해도 구성 영역만 영향을 받고, 사용 영역은 전혀 영향을 받지 않는다public class AppConfig {
...
private DiscountPolicy discountPolicy() {
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
...
}
AppConfig
에서 할인 정책 역할을 담당하는 구현을 FixDiscountPolicy
RateDiscountPolicy
객체로 변경했다.AppConfig
만 변경하면 된다. OrderServiceImpl
를 포함해서 사용 영역의 어떤 코드도 변경할 필요가 없다.AppConfig
를 애플리케이션이라는 공연의 기획자로 생각하면, 공연 기획자는 공연 참여자인 구현 객체들을 모두 알아야 한다"한 클래스는 하나의 책임만 가져야 한다"
“소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.”
“추상화에 의존해야하며, 구체화에 의존하면 안된다.”
OrderServiceImpl
은 DiscountPolicy
인터페이스에 의존한다.DiscountPolicy
구현객체가 올지 모른다.OrderServiceImpl
은 어떤 인터페이스에 의존하는지는 알 수 있으나, 어떤 구현객체가 주입될지는 알 수 없다OrderServiceImpl
이 어떤 구현객체를 의존하는지 알 수 있다.@Configuration
public class AppConfig {
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
public class MemberApp {
public static void main(String[] args) {
// AppConfig appConfig = new AppConfig();
// MemberService memberService = appConfig.memberService();
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
...
}
}
public class OrderApp {
public static void main(String[] args) {
// AppConfig appConfig = new AppConfig();
// MemberService memberService = appConfig.memberService();
// OrderService orderService = appConfig.orderService();
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
...
}
}
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
ApplicationContext
를 스프링 컨테이너라 한다.21:39:43.568 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig'
21:39:43.578 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'memberService'
21:39:43.599 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'memberRepository'
21:39:43.602 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'orderService'
21:39:43.605 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'discountPolicy'
@Configuration
이 붙은 AppConfig
를 설정(구성) 정보로 사용한다. @Bean
이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다 (로그). @Bean
이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다.MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
출처: 인프런 스프링 핵심 원리 - 기본편 (김영한)
인프런 스프링 핵심 원리