16. 새로운 구조와 할인정책 적용, 전체 흐름 정리
지난 포스트까지 각 인터페이스 구현체를 생성하고 이를 연결하는 AppConfig를 작성하였다.
https://debuggingworld.tistory.com/93
15. AppConfig 리팩터링
지난 포스트에서 AppConfig를 추가하여 구현 객체를 생성하고 연결하는 역할을 분리하였다. 근데 AppConfig 코드를 보면, 중복되는 부분이 보이고, 역할에 따른 구현이 한 눈에 잘 들어오지 않는다. pa
debuggingworld.tistory.com
이제 새롭게 추가된 AppConfig를 통해 실제로 새로운 할인 정책을 적용해본다.
새로운 구조와 할인 정책 적용
처음으로 돌아가, 기존 정액 할인 정책을 정률 할인 정책으로 변경해본다.
즉 DiscointPolicy의 구현체를 FixDiscountPolicy에서 RateDiscountPolicy로 변경한다.
AppConfig를 추가하여 구현체 생성 및 연결의 책임을 분리했기 때문에, 이러한 할인 정책의 변경은 놀라울 정도로 간단하다.
AppConfig의 discountPolicy() 메서드만 한 줄 바꿔주면 된다.
AppConfig.java
package hdxian.hdxianspringcore;
import hdxian.hdxianspringcore.discount.DiscountPolicy;
import hdxian.hdxianspringcore.discount.FixDiscountPolicy;
import hdxian.hdxianspringcore.discount.RateDiscountPolicy;
import hdxian.hdxianspringcore.member.MemberRepository;
import hdxian.hdxianspringcore.member.MemberService;
import hdxian.hdxianspringcore.member.MemberServiceImpl;
import hdxian.hdxianspringcore.member.MemoryMemberRepository;
import hdxian.hdxianspringcore.order.OrderService;
import hdxian.hdxianspringcore.order.OrderServiceImpl;
// 애플리케이션 전체를 설정하고 구성함.
// 앞으로 애플리케이션에 대한 환경 설정은 모두 이 클래스에서 수행한다.
public class AppConfig {
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public DiscountPolicy discountPolicy() {
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
}
discountPolicy()가 리턴하는 객체를 RateDiscountPolicy로 변경했기 때문에, OrderServiceImpl 객체를 생성할 때 주입되는 DiscountPolicy 구현체도 RateDiscountPolicy로 변경된다.
바꿔준 상태에서 테스트 메서드와 OrderApp을 실행해보면, 아무 문제 없이 정상 동작하는 것을 확인할 수 있다.
특히, OrderApp은 기존 1000원 할인에서 10% (2000원) 할인으로 잘 변경된 것을 확인할 수 있다.
중요한 점은, 이렇게 DiscountPolicy의 구현체를 변경해도 DiscountPolicy를 의존하는 OrderServiceImpl에는 아무련 변경이 없었다는 점이다.
OrderServiceImpl은 오직 DiscountPolicy에만 의존하며 (DIP 준수), DiscountPolicy의 구현체가 변경되어도 영향받지 않는다 (OCP 준수).
즉 AppConfig를 통해 객체의 생성과 연결을 관리함으로써, 객체 지향 원칙을 지키며 프로그램을 개발할 수 있게 되었다.
이러한 구조(AppConfig가 추가된 구조)를 통해 어플리케이션이 크게 객체를 사용하는 사용 영역과, 객체를 생성하고 구성하는 구성 영역으로 분리되었다.
여기서 방금과 같이 할인 정책을 변경하면, 구조는 다음과 같이 변경된다.
이러한 사용과 구성 영역의 분리를 통해, FixDiscountPolicy를 RateDiscountPolicy로 변경해도 구성 영역만 영향을 받을 뿐, 사용 영역은 전혀 영향받지 않는다.
구성 영역은 당연히 객체의 생성 및 연결을 관리하므로 구현 객체들을 모두 알아야 하고, 기능 확장(새로운 구현 객체 추가) 등의 변경이 발생하면 변경된다.
전체 흐름 정리
섹션 3의 첫 내용부터 지금까지가 모두 이어진 내용이었다.
그러한 탓에 같은 이야기를 여러번 하기도 하고, 중요한 내용을 놓칠 수도 있기 때문에 지금까지의 내용을 전체적으로 정리해본다.
사실 좀 더 크게 보자면 섹션 2부터 생각해볼 수 있다.
섹션 2에서 우리는 스프링 기능을 이용하지 않고 순수 자바 코드로만 객체 지향 프로그램을 작성해보았다.
그 목적은 스프링 없이 자바로만 객체 지향 프로그래밍을 해보면서, 스프링의 등장 배경과 필요성을 이해하기 위해서였다.
객체 지향 프로그래밍을 하기 위해서 무엇을 고려해야 하는지 이해해야, 이를 도와주는 스프링이 어디에 쓰이는건지 이해할 수 있기 때문이다.
섹션 2에서는 자바의 객체 지향적 특성인 다형성을 이용해 문제없이 프로그램을 잘 만든 것 같아 보였으나, 섹션 3부터 새로운 할인 정책을 도입하려다 보니 무언가 문제가 있었다.
새로운 할인 정책 개발
다형성 덕분에 새로운 할인 정책 코드 (정률 할인 정책)를 추가로 개발하는 것에는 문제가 없었다.
새로운 할인 정책 적용과 문제점
하지만 새로 개발한 정률 할인 정책을 적용하려니 몇가지 문제가 있었다.
- 클라이언트 코드인 주문 서비스 구현체 (OrderServiceImpl)도 함께 변경해야 한다. (OCP 위반)
- 이는 주문 서비스 클라이언트가 인터페이스인 DiscountPolicy 뿐 아니라 FixDiscountPolicy와 같은 구현 클래스에도 의존하기 때문이다. (DIP 위반)
관심사의 분리
이 문제의 해결법을 찾기 위해 어플리케이션을 하나의 공연으로 비유해보자.
각 인터페이스는 공연의 배역이고, 구현 객체는 배역을 연기할 실제 배우다.
기존 클라이언트가 구현체를 직접 생성하고 실행하는 것은, 공연의 배우가 자기 상대 배역의 배우를 직접 캐스팅하러 다니는 것과 같다.
따라서 공연 구성 및 담당 배우 섭외를 전담할 별도의 공연 감독, 혹은 기획자가 필요하다.
어플리케이션에서도 이런 역할을 담당할 AppConfig 클래스를 추가해보자.
AppConfig는 어플리케이션의 전체 동작을 구성하기 위해 구현 객체를 생성하고, 이를 연결하는 책임을 가진다.
이를 통해 클라이언트 객체는 자신이 의존하는 객체에 대해 신경쓰지 않고, 실행에만 집중할 수 있다. (책임이 명확해진다.)
+) 의존, 의존성, 의존관계
의존: 어떤 객체가 기능을 실행하기 위해 다른 요소를 필요로 하는 것.
OrderServiceImpl을 예로 들자면, OrderServiceImpl은 회원 등급을 조회하기 위해 MemberRepository가 필요하고, 할인을 적용하기 위해 DiscountPolicy가 필요하다.
여기서 OrderService는 MemberRepository와 DiscountPolicy에 의존한다 라고 표현한다.
그리고 의존하는 객체 간의 관계 또는 의존하는 객체 자체를 의존성, 의존관계라 표현한다.
OrderServiceImpl은 MemberRepository, DiscountPolicy에 의존성(또는 의존관계)를 갖는다.
AppConfig 적용 이전의 경우, OrderServiceImpl은 구현체인 MemoryMemberRepository, FixDiscountPolicy에도 의존했다. (또는 의존성을 가졌다.)
AppConfig 리팩터링
MemberRepository, DiscountPolicy의 구현 객체를 리턴하는 메서드를 추가로 작성하였다.
이를 통해 구성 정보에서 역할과 구현을 명확하게 분리하고, 중복을 제거하였다.
새로운 구조와 할인 정책 적용
추가된 AppConfig를 바탕으로 원래 하려던 새로운 할인 정책을 적용하였다.
AppConfig가 추가됨으로써 어플리케이션이 크게 사용 영역과 구성 영역으로 분리되었다.
할인 정책을 변경해도 AppConfig가 있는 구성 영역만 변경하면 되고, 사용 영역은 변경할 필요가 없다.
물론 클라이언트인 주문 서비스 코드 (OrderServiceImpl)도 사용 영역이므로 변경할 필요가 없어졌다.
섹션 2부터 이어진 전체 내용을 정리해보았다.
다음 포스트부터는 객체 지향의 5가지 원칙 (SOLID)를 검토해보고, 이러한 구조에서 스프링이 무엇을 해줄 수 있는지 알아본다.