할인 정책을 변경하려고 봤더니 클라이언트격인 OrderServiceImpl 코드를 고쳐야한다.
이것은 OCP를 위반한 것이다.
그리고 또 위 코드를 자세히 살펴보면 인터페이스만 의존한 것 같지만 사실 new
예약어 오른쪽인 구현체에도 의존하고 있다.
즉, FixDiscountPolicy 구현체에서 RateDiscountPolicy 구현체로 바꾸는 순간 Impl의 코드를 거의 바꿔야한다는 것이다.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
애플리케이션의 전체 동작 방식을 config하기 위한 구현체를 생성하고, 연결하는 책임을 갖는 별도의 설정 클래스를 이용하여 위 2 문제를 해결할 수 있다.
즉, AppConfig가 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.
그리고 AppConfig가 생성한 객체 인스턴스의 래퍼런스를 생성자를 통해서 주입(연결)해준다.
이렇게 되면 앞서 작성했던 클라이언트(~~Impl) 클래스에서는 의존관계에 대한 고민은 필요없고 실행에만 집중할 수 있게 된다.
public class OrderServiceTest {
// AppConfig appConfig = new AppConfig();
// MemberService memberService = appConfig.memberService();
// OrderService orderService = appConfig.orderService();
MemberService memberService;
OrderService orderService;
@BeforeEach
public void beforeEach() {
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
orderService = appConfig.orderService();
}
@Test
void createOrder() {
Long memberId = 1L;
Member member = new Member(memberId, "MemberA", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "Item A", 10000);
Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
}
public class OrderApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
// MemberService memberService = new MemberServiceImpl();
MemberService memberService = appConfig.memberService();
// OrderService orderService = new OrderServiceImpl();
OrderService orderService = appConfig.orderService();
Long memberId = 1L;
Member member = new Member(memberId, "MemberA", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "Item A", 10000);
}
}
App"Config" 즉, 구성,설정 정보인데 위 코드를 봐도 위 사진처럼 한 눈에 들어오지 않는다는 것이다.
그래서 사실 이러한 역할들을 드러나게 작성하는 것이 중요하다.
opt + cmd + m
, window : ctrl + alt + m
메서드 리팩토링으로 따로 메서드를 추출하였다.public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(getMemberRepository());
}
private static MemoryMemberRepository getMemberRepository() {
return new MemoryMemberRepository();
}
public OrderService orderService() {
return new OrderServiceImpl(getMemberRepository(), discountPolicy());
}
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
// return new FixDiscountPolicy();
}
}
그래서 위 코드만 보고 아 getMemberRepository()
가 실제 Impl에 들어갈 구현체구나! 를 알 수 있고
나중에 로직이 바뀌어서 메서드가 바뀌어도 2,4번째 생성해주는 메서드만 바꾸면 된다.
그래서 만약 할인 정책이 바뀌었다면 코드 딱 1줄만 바꾸면 되는 것이다.
~impl
)는 실행하는 책임만 담당한다.DisCountPolicy
추상화 인터페이스만 의존하도록 하였다. 이때 클라이언트 코드는 인터페이스만으로는 아무것도 실행할 수 없으므로--DiscountPolicy
객체 인스턴스를 클라이언트 코드 대신 생성하여 클라언트와 의존관계를 주입했다.