기존에 정액할인 정책을 사용하다가 정률 할인 정책으로 변경하는 상황
public class OrderServiceImpl implements OrderService {
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
OrderServiceImpl
에서 FixDiscountPolicy()
를 RateDiscountPolicy()
로 변경해줬다.
DIP를 위반한다.
주문서비스 클라이언트(OrderServiceImple)은 DiscountPolicy인터페이스(추상)뿐만 아니라 구체(구현)클래스에도 의존하고 있다.
추상(인터페이스) : DiscountPolicy
구체(구현) 클래스 : FixDiscountPolicy , RateDiscountPolic
OCP를 위반한다.
지금 코드는 기능을 확장해서 변경하면, 클라이언트(OrderServiceImple) 코드부분에서 RateDiscountPolicy로 변경하므로 클라이언트 코드에 영향을 준다.
public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
}
문제점
위와 같이 인터페이스에만 의존 했지만 discountPolicy에 아무것도 없어서 null pointer exception이 뜨게 된다.
해결방안
이 문제를 해결하려면 누군가가 클라이언트인 OrderServiceImpl
에 DiscountPolicy
의 구현 객체를 대신 생성하고 주입해주어야 한다.
위 코드를 공연에 비유하면 로미오 역할(인터페이스)을 하는 배우(구현체)가 줄리엣 역할을 하는 배우를 직접 캐스팅하는 것과 같다.
배우는 배역에만 집중하고, 공연과 캐스팅을 담당하는 기획자가 필요하다.
AppConfig
public class AppConfig {
//MemberService 역할
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
//OrderService 역할
public OrderService orderService() {
return new OrderServiceImpl(
memberRepository(),
discountPolicy());
}
//MemberRepository 역할
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
// DiscountPolicy 역할
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
}
AppConfig
는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.
AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결)해준다.
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
/* AppConfig에서 생성자를 통해 구체클래스를 주입해서
추상(인터페이스)에만 의존한다. -> DIP 지킴!
*/
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
MemberServiceImpl
은 MemberRepository
인 추상에만 의존하면 된다. 이제 구체 클래스를 몰라도 된다.Appconfig
에서 객체를 생성하고 그 참조값을 serviceImpl
을 생성하면서 생성자로 전달한다.
→클라이언트인 serviceImpl
입장에서 보면 의존관계를 외부에서 주입하는 것과 같다고 해서 DI(의존성 주입)이라 한다
DIP 해결
AppConfig
가 구현객체를 생성해서 클라이언트의 생성자를 통해 의존관계를 주입해 주면서 DIP문제가 해결됨
OCP 해결
이제 OrderServiceImpl
에서 FixDiscountPolicy
→ RateDiscountPolic
로 변경하던 것을 AppConfig
에서 변경하면 되므로 사용영역의 코드를 변경할 필요가 없어 졌다.
기존 프로그램은 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고, 연결하고, 실행했다.(구현 객체가 프로그램의 제어 흐름을 스스로 조종했다)
반면에 AppConfig가 등장한 이후에 구현 객체는 자신의 로직을 실행하는 역할만 담당한다. 프로그램의 제어 흐름은 이제 AppConfig가 가져간다. 예를 들어서 OrderServiceImpl 은 필요한 인터페이스들을 호출하지만 어떤 구현 객체들이 실행될지 모른다.
이렇듯 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)이라 한다
@Configuration
public class AppConfig {
//MemberService 역할
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
//MemberRepository 역할
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
//OrderService 역할
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
// DiscountPolicy 역할
@Bean
public DiscountPolicy discountPolicy(){
// AppConfig에서 RateDiscountPolicy로만 교체하면 됨
return new RateDiscountPolicy();
}
}
public class MemberApp {
public static void main(String[] args) {
// 순수한 자바로 AppConfig 사용했을떄
// AppConfig appConfig = new AppConfig();
//MemberService memberService = appConfig.memberService();
//OrderService orderService = appConfig.orderService();
//스프링으로 AppConfig 사용했을때
// 스프링 컨테이너에 Bean들을 넣고 관리해줌
ApplicationContext ac= new AnnotationConfigApplicationContext(AppConfig.class);
//getBean(@Bean의 이름, 반환 타입)으로 필요한 스프링 빈 찾기
MemberService memberService = ac.getBean("memberService", MemberService.class);
OrderService orderService = ac.getBean("orderService", OrderService.class);
Long memberId = 1L;
Member member = new Member(memberId,"memberA", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "itemA", 10000);
System.out.println(order.toString());
}
}
ApplicationContext
를 스프링컨테이너라고 한다.AppConfig
로 직접 DI를 했지만 이제부터는 스프링컨테이너를 통해 한다.@Configuration
이 붙은 AppConfig를 설정 정보로 사용한다. 여기서 @Bean
이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다