
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를 지킨 것 같아보인다.discountPolicyFixDiscountPolicy, RateDiscountPolicyOrderServiceImpl가 인터페이스 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());
}
}
MemberServiceImplMemoryMemberRepositoryOrderServiceImplFixDiscountPolicyMemberServiceImpl MemoryMemberRepositorOrderServiceImpl MemoryMemberRepositor, FixDiscountPolicypublic 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역할에 대한 구현은 MemberServiceImplMemberRepository역할에 대한 구현은 MemoryMemberRepositoryOrderService역할에 대한 구현은 OrderServiceImplDiscountPolicy역할에 대한 구현은 RateDiscountPolicyFixDiscountPolicy를 선택할 경우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);
출처: 인프런 스프링 핵심 원리 - 기본편 (김영한)
인프런 스프링 핵심 원리