//private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
위 코드에서의 문제점으로는 정액 할인 정책에서 정률 할인 정책으로 변경시,
추상화(인터페이스), 구체클래스(할인 정책 구현 클래스) 모두 의존 한다.
실제 의존관계
잘보면 클라이언트인 OrderServiceImpl 이 DiscountPolicy 인터페이스 뿐만 아니라
FixDiscountPolicy 인 구체 클래스도 함께 의존하고 있다. 실제 코드를 보면 의존하고 있다! DIP 위반사항이다.
중요!: 그래서 FixDiscountPolicy 를 RateDiscountPolicy 로 변경하는 순간 OrderServiceImpl 의 소스 코드도 함께 변경해야 한다! OCP 위반하였다.
예를 들어 보자면,
공연을 예로 들어 로미오 역할(인터페이스)을 하는 디카프리오라는 남자 배우(구현체)가 줄리엣 역할(인터페이스)을 하는 여자 주인공(구현체)를 초빙하는 것과 같으며 디카프리오는 공연도하고 여자 주인공도 초빙하는 다양한 책임을 가지게 된다.
역할에 맞는 배우를 지정하는 책임을 담당하는 별도의 공연 기획자가 필요하고, 배우와 공연 기획자의 책임을 확실히 분리해야함. (관심사 분리)
해결방안 (관심사 분리)
애플리케이션의 전체 동작 방식을 구성(config)하기 위해, '구현 객체를 생성' 하고, '연결' 하는 책임을 가지는 별도의 설정 클래스를 만들기
public class AppConfig {
public MemberService memberService() {
// 생성자 주입
return new MemberServiceImpl(memberRepository());
}
private MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
private DiscountPolicy discountPolicy() {
/*할인 정책을 변경할 때에는 return 값만 바꿔주면 된다.*/
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
애플리케이션의 실제 동작에 필요한 '구현 객체를 생성' 한다.
AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결)해준다.
이렇게 된다면 MemberServiceImpl 은 이제부터 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중하면 된다.
즉, DIP 완성 - MemberServiceImpl 은 MemberRepository 인 추상에만 의존하면 된다. 이제 구체 클래스를
몰라도 된다.
여기서는 3가지 SRP , DIP , OCP 적용됨.
- SRP 단일 책임 원칙 : 한 클래스는 하나의 책임만 가져야 한다.
- SRP 단일 책임 원칙을 따르면서 관심사 분리
- AppConfig: 구현 객체를 생성하고 연결하는 책임
- 클라이언트 객체: 실행하는 책임
- DIP 의존관계 역전 원칙 : 프로그래머는 "추상화에 의존해야지, 구체화에 의존하면 안된다." 의존성 주입은 이 원칙을 따르는 방법 중 하나다.
- 클라이언트 코드가 추상화 인터페이스, 구체화 구현 클래스 함께 의존 했었지만, AppConfig가 구체화 구현 클래스 인스턴스를 클라이언트 코드 대신 생성해서 클라이언트 코드에 의존관계를 주입함으로써 클라이언트 코드는 추상화 인터페이스만 의존할 수 있게 되어 DIP 원칙을 지켰다.
- OCP 개방-폐쇄 원칙 : 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
- AppConfig가 의존관계인 할인 정책을 변경해서 클라이언트 코드에 주입하므로 클라이언트 코드는 변경하지 않아도 된다.