슬픈 야옹이 2023. 10. 13. 14:56

지난 포스트에서 추가한 새로운 할인 정책을 적용해보자.

https://debuggingworld.tistory.com/90

 

12. 새로운 할인 정책 개발

서비스 개발 과정에서, 다음과 같은 상황이 발생했다고 가정해보자. 기획자: 회의를 해봤는데, 아무래도 할인 정책을 바꿔야 할 것 같아요. 고정 금액 할인보다는 전체 구매가에서 일정 비율로

debuggingworld.tistory.com

 

 

새로운 할인 정책 적용과 문제점

OrderServiceImpl을 수정하여 새로운 할인 정책을 적용한다.

OrderServiceImpl.java

 

OrderServiceImpl.java

package hdxian.hdxianspringcore.order;

import hdxian.hdxianspringcore.discount.DiscountPolicy;
import hdxian.hdxianspringcore.discount.FixDiscountPolicy;
import hdxian.hdxianspringcore.discount.RateDiscountPolicy;
import hdxian.hdxianspringcore.member.Member;
import hdxian.hdxianspringcore.member.MemberRepository;
import hdxian.hdxianspringcore.member.MemoryMemberRepository;

public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository = new MemoryMemberRepository();
    // private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();


    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        // OrderService는 할인에 관여하지 않고 discountPolicy에 member정보를 넘기기만 한다.
        // 단일 책임 원칙이 잘 지켜진 사례. 할인 정책이 변경되어도 OrderService는 변화가 없음.
        int discountPrice = discountPolicy.discount(member, itemPrice); // discount()는 할인 액수를 리턴함.

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }

}

 

근데 뭔가 이상하다. 변경된 것은 할인 정책인데, OrderServiceImpl 코드에 변화가 일어났다.

 

분명 어플리케이션을 설계할 때 역할과 구현을 분리하였고, 이를 인터페이스와 구현 객체를 통해 잘 구현한 것 같은데 말이다.

 

이는 지금까지 작성한 프로그램이 OCP, DIP와 같은 객체 지향 원칙을 충실히 준수한 것이 아니라는 의미다.

 

이러한 문제가 발생하는 이유는, 지금의 OrderServiceImpl이 MemberRepository, DiscountPolicy와 같은 인터페이스는 물론, MemoryMemberRepositroy, RateDiscountPolicy와 같은 구현체까지 의존하고 있기 때문이다.

 

즉 우리가 설계하려던 의존 관계가 다음과 같다면,

기존에 의도한 의존 관계 (강의자료 발췌)

 

 

지금 실제로 구현된 의존 관계는 다음과 같은 상황이다.

실제로 구현된 의존 관계 (강의자료 발췌)

 

 

여기서 적용할 할인 정책이 고정 할인에서 정률 할인으로 변경되면 다음과 같이 변경된다.

할인 정책이 변경된 경우의 의존 관계 (강의자료 발췌)

정리하자면, 지금까지 우리가 작성한 프로그램은 객체 지향 원칙이 제대로 지켜지지 않았으며, 자세한 이유는 다음과 같다.

  • OrderServiceImpl이 구현체에도 의존하고 있다. -> DIP 위반
    • OrderServiceImpl이 DiscountPolicy, MemberRepository의 기능을 이용하려면 각 인터페이스의 구현체가 뭔지 직접 적어줘야 한다. (new MemoryMemberRepository(), new RateDiscountPolicy() 등)
  •  다른 요소에 의해 OrderServiceImpl의 코드에 변경이 일어난다. -> OCP 위반
    • FixDiscountPolicy를 RateDiscountPolicy로 변경하는 순간 OrderService의 코드도 변경되어야 한다.

 

+)

  • DIP (Dependancy Inversion Principle, 의존관계 역전 원칙): 프로그램은 구현이 아닌 추상에 의존해야 한다.
  • OCP (Open-Closed Principle, 개방-폐쇄 원칙): 프로그램은 확장에는 열려있고, 변경에는 닫혀 있어야 한다.

https://debuggingworld.tistory.com/80

 

3. 좋은 객체 지향 설계의 5가지 원칙 (SOLID)

지난 포스트에서 좋은 객체 지향 프로그래밍이란 프로그램의 유연한 변경이 가능하도록 하여 개발 생산성을 높이는 것이라 하였다. 그리고 이것을 위해서는 객체 지향의 다형성을 극대화하고,

debuggingworld.tistory.com

 

 

해결법(?)

그렇다면 이러한 문제를 어떻게 해결해야 할까?

일단 코드를 객체 지향 원칙에 맞게 다시 고쳐보자.

 

OrderServiceImpl을 인터페이스에만 의존하도록 수정한다.

OrderServiceImpl.java

 

OrderServiceImpl.java

package hdxian.hdxianspringcore.order;

import hdxian.hdxianspringcore.discount.DiscountPolicy;
import hdxian.hdxianspringcore.discount.FixDiscountPolicy;
import hdxian.hdxianspringcore.discount.RateDiscountPolicy;
import hdxian.hdxianspringcore.member.Member;
import hdxian.hdxianspringcore.member.MemberRepository;
import hdxian.hdxianspringcore.member.MemoryMemberRepository;

public class OrderServiceImpl implements OrderService {

//    private final MemberRepository memberRepository = new MemoryMemberRepository();
//    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        // OrderService는 할인에 관여하지 않고 discountPolicy에 member정보를 넘기기만 한다.
        // 단일 책임 원칙이 잘 지켜진 사례. 할인 정책이 변경되어도 OrderService는 변화가 없음.
        int discountPrice = discountPolicy.discount(member, itemPrice); // discount()는 할인 액수를 리턴함.

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }

}

 

찝찝하지만 이 상태로 테스트를 돌려보자.

테스트

 

당연히 NPE (Null Pointer Exception)이 발생할 것이다.

테스트 실패

OrderServiceImpl에서 memberRepository, discountPolicy의 메서드를 호출하지만, 두 참조변수는 실제로 아무 구현체도 참조하고 있지 않기 때문이다.

 

답이 없어보인다. 객체 지향 원칙을 지키려면 구현체에 의존하지 않도록 만들어야 하는데,

구현체에 의존하지 않으면서 구현체의 기능을 어떻게 사용한단 말인가?

 

이 문제를 해결하려면 외부에서 누군가가 대신 구현체를 전달해주어야 한다.

 

다음 포스트에서는 OrderServiceImpl에 DiscountPolicy의 구현 객체를 대신 생성하고 주입해주는 외부 요소를 만들어 문제를 해결해본다. 다음 포스트는 강의에서도 강조했던 중요한 파트로, 좀 더 집중이 필요한 내용이 될 것이다.