해당 내용은 김영한님
스프링 핵심 원리 - 기본편
강의를 보고 작성되었습니다!
이전 강의 내용인 서비스로직, 비즈니스로직과 대충의 컨트롤러는
예제 만들기 파트이기 때문에,
구현했다는 가정 하에 설명드리겠습니다! :)
개발할 전체 다이어그램은 다음과 같습니다.
이전에는 DiscountPolicy 인터페이스를 상속받는
FixDisCountPolicy를 만들었다.
고정 할인 정책에서,
변동 할인 정책(RateDiscountPolicy)으로 정책을 변경할 예정이다.
RateDiscountPolicy를 다음과 같이 추가했다.
@Component
public class RateDiscountPolicy implements DiscountPolicy{
private int discountPercent = 10;
@Override
public int discount(Member member, int price) {
if(member.getGrade() == Grade.VIP){
return price * discountPercent / 100;
}
else{
return 0;
}
}
}
그리고, 나는 할인 정책을 선택하는 OrderServiceImpl에서, 코드를 다음과 같이 변경했다.
public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
즉, 고정할인정책에서, 변동할인정책으로 코드를 수정한것이다.
이 코드에는 문제가 있다.
SOLID원칙의 D, 의존 역전 원칙에 위배된 코드이다.
겉보기에는
OrderServiceImpl클래스가
DiscountPolicy인터페이스를 DI받으므로 문제없는것같지만,
DiscountPolicy 인터페이스는 인터페이스 그 자체가 아니라, RateDiscountPolicy 라는 구체화된 클래스를 구현했다.
OrderServiceImpl클래스는 RateDiscountPolicy 라는 구체화된 클래스를 의존하고 있다.
위의 그림과 같은 구조인 셈이다.
그럼 바꿔주는 방법은 간단하다.
OrderServiceImpl클래스가
DiscountPolicy인터페이스 자체를 DI받도록 하고,
DiscountPolicy인터페이스가 어떤 클래스로 구체화될지는,
애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스인
AppConfig에서 설정해준다.
즉, AppConfig 클래스를 새로 만들어
A라는 인터페이스를 DI받는 클래스들에게,
A를 구체화한 클래스들(a1, a2, a3, ..) 중에서
원하는 구체 클래스를 선택하도록 만들겠다! 라는 이야기다.
그렇게 만든 AppConfig 클래스는 아래와 같다.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
이렇게 AppConfig 클래스를 구현해주면, 아래와 같이
OrderServiceImpl 클래스 자체에서는 단순 인터페이스만 상속받도록
코드를 수정할 수 있다.
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
OrderServiceImpl 클래스 에서는, 어떤 할인 정책이 들어올지 모르게 되었다는 소리이고,
오직 AppConfig 클래스에서 어떤 구체 클래스가 들어갈지 결정되게 되었다.
아래의 사진과 같은 상황이 된 것이다.
이렇게 DIP가 완성되었다.
지금은 코드가 다음과 같다.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
멤버서비스와 오더서비스 안에서, 각각 db 정책과 할인 정책이 다 들어가있다.
해당 코드에서는 내가 인메모리 repository에서 db로 바꾸고싶다면,
멤버서비스 오더서비스 두개의 매서드 둘 다에서 코드를 수정해아한다.
해당 코드를 아래와 같이 바꾼다.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(),discountPolicy());
}
//위는 서비스, 아래는 인터페이스 선택
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
}
리팩터링된 코드는,
서비스를 반환하는 매서드는 단순히 인터페이스들을,
(memberService, orderService)
해당 인터페이스의 구현체를 반환하는 매서드들을 만들어서 더 가독성이 좋게 되었다.
(memberRepository, discountPolicy)
즉, AppConfig 를 보면 역할과 구현 클래스가 한눈에 들어온다.
애플리케이션 전체 구성이 어떻게 되어있는지
빠르게 파악할 수 있게 되었고, 해당 코드에서는 내가 인메모리 repository에서 db로 바꾸고싶다면,
각각의 코드 수정이 아닌 멤버리파지토리 매서드만 수정하면 되도록 바뀌었다.
이렇게 수정해준 후,
처음으로 돌아가보자.
이제는,
AppConfig에서 FixDiscountPolicy(); 부분을,
RateDiscountPolicy(); 로만 바꿔준다면 (아래 코드)
구성 영역(Config)만 영향을 받고, 사용 영역(service)은 전혀
영향을 받지 않게 되었다.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(),discountPolicy());
}
//위는 서비스, 아래는 인터페이스 선택
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public DiscountPolicy discountPolicy() {
//return new FixDiscountPolicy();
return new RateDiscountPolicy();
//여기 윗부분 변경!
//여기 윗부분 변경!
//여기 윗부분 변경!
//여기 윗부분 변경!
}
}
즉, 구성의 설계도를 AppConfig라고 생각하고, 잘 맞물리며 돌아가는 톱니바퀴들중 하나만 빼서 교체(할인정책 변경)해도 다시 잘 돌아가는 구성을 만들었다.
코드 변경을 통해 변경된 전체 구성을 한눈에 정리하자면 위의 사진과 같다.
지금까지의 코드 변경으로 SOLID원칙의
D : 의존 역전 원칙과 (클래스 의존 -> 인터페이스 의존)
S : 단일 책임 원칙 (OrderServiceImpl 에서 더이상 구체적인 할인 정책을 고르지 않음)
O : 개방폐쇄의 원칙 (인터페이스를 구체화하는 여러 구체 클래스를 통해 확장을 열어둠)
등등의 내용을 학습했다.
소스코드
https://github.com/ingeon2/coreofspring-SOLID
레퍼런스 : 김영한님 pdf