이전 게시글 예제 만들기에서 OCP와 DIP를 위반하였다. 바로 OrderServiceImple이 DiscountPolicy의 인터페이스 뿐만 아니라 구체 클래스도 함께 의존했기 때문이다.
OrderServiceImpl class
//주문 서비스 구현체
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
// 객체를 바꿔끼울때 구체 구현 클래스에도 의존중.. OCP가 위반되었다.
// private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId); //회원 정보 조회
int discountPrice = discountPolicy.discount(member, itemPrice); // 할인 정책 확인
return new Order(memberId, itemName, itemPrice, discountPrice); //최종 주문 반환
};
}
AppConfig의 추가
- 애플리케이션을 하나의 공연이라 생각하면 AppConfig는 공연 기획자이다. 로미오와 줄리엣의 역할이 있다면 로미오의 배우, 줄리엣의 배우를 섭외하는 것이다.
- 애플리케이션의 전체 동작 방식을 구성하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스이다.
AppConfig Class
public class AppConfig {
//역할
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
//구현
//메서드명이 드러나도록 리팩토링, Service에서 사용할 객체를 바꾸려면 이 코드를 고치면된다.
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
//역할
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
//구현
public DiscountPolicy discountPolicy(){
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
OrderServiceImple을 직접 구현체를 받지 않고, 외부에서 객체를 받을 수 있게 변경한다.
OrderServiceImple class
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;
}
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId); //회원 정보 조회
int discountPrice = discountPolicy.discount(member, itemPrice); // 할인 정책 확인
return new Order(memberId, itemName, itemPrice, discountPrice); //최종 주문 반환
};
}
이제부터 OrderServiceImpl(구현체)은 Constructor(생성자를) 통해 외부에서 객체를 끼울 수 있도록 한다. 그러면 OrderService는 MemberRepository, DiscoutPolicy인 추상에만 의존하면 된다. 한마디로 구체 클래스는 구체 클래스를 몰라도 된다.
AppConfig 객체는 객체 자체를 생성하고 그 참조값을 구현체를 생성하면서 생성자로 전달한다. 구현체의 입장에서 보면 의존관계를 마치 외부에서 주입해주는 것 같다고 하여 DI(Dependency Injection), 의존성 주입이라 한다.
MemberApp
public class MemberApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig(); // AppConfig로 접근
MemberService memberService = appConfig.memberService(); // 객체를 생성
Member member = new Member(1L, "mamberA", Grade.VIP);
memberService.join(member);
Member findMember = memberService.findMember(1L);
System.out.println("newMember = " + member.getName()); // 가입한 멤버
System.out.println("findMember = " + findMember.getName()); // 멤버 확인
}
}
한 애플리케이션에서 사용할 때 AppConfig로 접근한다.
새로운 할인 정책 적용
정액 할인 에서 정률 할인 정책으로 변경해보자.
/*
* AppConfig의 일부 코드
*/
public class AppConfig {
//역할
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
//구현
public DiscountPolicy discountPolicy(){
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
할인 정책을 변경해도 AppConfig가 있는 구성 영역만 변경하면 된다. 구현 역할이 되어 있는 DiscountPolicy를 RateDiscountPolicy 객체로 바꿔 끼워 주기만 하면된다.
AppConfig의 등장으로 애플리케이션이 크게 사용 영역과, 객체를 생성하고 구성하는 영역으로 분리가 되었다.
한마디로 역할인 인터페이스는 직접적으로 구현체를 의존하지 않는다. 이제 새로운 정책, 새로운 코드가 필요할 때 구현체를 수정하지 않고 새로운 구현체를 생성하여 외부에서 객체를 바꿔 끼울 수 있다.