ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [김영한 스프링] 06. 새로운 할인 정책 적용과 문제점, 관심사의 분리
    Spring/스프링 핵심 원리 - 기본편 2023. 4. 12. 00:47

    할인 정책을 변경하려면 클라이언트인 OrderServiceImpl의 코드를 고쳐야 한다.

     

    문제점 발견

    • 우리의 역할과 구현을 충실하게 분리했다 -> OK
    • 다형성도 활용하고, 인터페이스와 구현 객체를 분리했다. -> OK
    • OCP, DIP 같은 객체지향 설계 원칙을 충실히 준수했다.
      • -> 그렇게 보이지만 사실은 아니다.
    • DIP : 주문서비스 클라이언트(OrderServiceImpl)는 DiscountPolicy 인터페이스에 의존하면서 DIP를 지킨 것 같은데?
      • -> 클래스 의존관계를 분석해 보자. 추상(인터페이스) 뿐만 아니라 구체(구현) 클래스에도 의존하고 있다.
        • 추상(인터페이스) 의존 : DiscountPolicy
        • 구체(구현) 클래스 : FixDiscountPolicy, RateDiscountPolicy
    • OCP : 변경하지 않고 확장할 수 있다고 했는데!
      • -> 지금 코드는 기능을 확장해서 변경하면, 클라이언트 코드에 영향을 준다! 따라서 OCP를 위반한다.

     

     

    정책변경

     

    어떻게 문제를 해결할 수 있을까?

    • 클라이언트 코드인 OrderServiceImpl은 DiscountPolicy의 인터페이스뿐만 아니라 구체 클래스도 함께 의존한다.
    • 그래서 구체 클래스를 변경할 때 클라이언트 코드도 함께 변경해야 한다.
    • DIP 위반 -> 추상에만 의존하도록 변경 (인터페이스에만 의존)
    • DIP를 위반하지 않도록 인터페이스에만 의존하도록 의존관계를 변경하면 된다.

     

     

    인터페이스에만 의존하도록 Private DiscountPolicy discountPolicy로 변경

     

    실행 시 NullPoint에러 발생

     

    관심사의 분리

    • 애플리케이션을 하나의 공연이라 생각해보자. 각각의 인터페이스를 배역이라 생각하자. 그런데! 실제 배역에 맞는 배우를 선택하는 것은 누가 하는가?
    • 로미오와 줄리엣 공연을 하면 로미오 역할을 누가 할지, 줄리엣 역할을 누가 할지는 배우들이 정하는 것이 아니다.
    • 이전 코드는 마치 로미오 역할(인터페이스)을 하는 레오나르도 디카프리오(구현체, 배우)가 줄리엣 역할(인터페이스)을 하는 여자 주인공(구현체, 배우)을 직접 초빙하는 것과 같다. 디카프리오는 공연도 해야하고 동시에 여자 주인공도 공연에 직접 초빙해야 하는 다양한 책임을 가지고 있다.

     

    관심사를 분리하자

    • 배우는 본인의 역할인 배역을 수행하는 것에만 집중해야 한다.
    • 디카프리오는 어떤 여자 주인공이 선택되더라도 똑같이 공연을 할 수 있어야 한다.
    • 공연을 구성하고, 담당 배우를 섭외하고, 역할에 맞는 배우를 지정하는 책임을 담당하는 별도의 공연 기획자가 나올시점이다.
    • 공연 기획자를 만들고, 배우와 공연 기획자의 책임을 확실히 분리하자.

     

    main/java/hello.core/AppConfig 생성

     

    지금까지 객체를 생성하고 '이 인터페이스에 어떤 것을 할당해야 해!'라는 것을 MemberServiceImpl이 직접함

    MemberServiceImpl안에 MemberRepository와 MemoryMemberRepository을 직접 지정함 -> 배우가 담당 배우를 직접 섭외하는 것과 같은 것이다.

    이것을 AppConfig에서 다 하도록 변경 해야함

     

    private final MemberRepository memberRepository = new MemoryMemberRepository();에서 = new MemoryMemberRepository() 제거

     

    생성자 생성

     

    memberRepository에 어떤 구현체가 들어갈지를 생성자를 통해서 확인

     

    new MemoryMemberRepository() 추가(생성자 주입)

     

    MemberServiceImpl에 MemoryMemberRepository는 없고 MemberRepository라는 인터페이스만 있다. -> 추상화에만 의존

     

    OrderService 추가

     

    생성자 생성

     

    인터페이스에만 의존 -> DIP 지키고 있음

    구체적인 클래스는 전혀 모름

    MemoryMemberRepository가 들어올지, DbMemoryRepository가 들어올지 전혀 모름

    대본 보고 연기하듯이 로직만 실행하면 됨

     

    AppConfig.java

    package hello.core;

    import hello.core.discount.FixDiscountPolicy;
    import hello.core.member.MemberService;
    import hello.core.member.MemberServiceImpl;
    import hello.core.member.MemoryMemberRepository;
    import hello.core.order.OrderService;
    import hello.core.order.OrderServiceImpl;

    public class AppConfig {
        
        public MemberService memberService() {
            return new MemberServiceImpl(new MemoryMemberRepository());
        }
        
        public OrderService orderService() {
            return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
        }
    }
    • 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.
      • MemberServiceImpl, MemoryMemberRepository, OrderServiceImpl, FixDiscountPolicy
    • 생성한 객체 인스턴스의 참고(레퍼런스)를 생성자를 통해서 주입(연결)해준다.
      • MemberServiceImpl -> MemoryMemberRepository
      • OrderServiceImpl -> MemoryMemberRepository, FixDiscountPolicy

     

    MemberServiceImpl.java

    package hello.core.member;

    public class MemberServiceImpl implements MemberService {
        
        private final MemberRepository memberRepository;
        
        public MemberServiceImpl(MemberRepository memberRepository) {
            this.memberRepository = memberRepository;
        }
        
        @Override
        public void join(Member member) {
            memberRepository.save(member);
        }
        
        @Override
        public Member findMember(Long memberId) {
            return memberRepository.findById(memberId);
        }
    }
    • MemoryMemberRepository를 의존하지 않는다.
    • MemberRepository 인터페이스만 의존한다.
    • 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다.
    • 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부(AppConfig)에서 결정된다.
    • 이제부터 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중하면 된다.

     

    그림 - 클래스 다이어그램

     

     

    OrderServiceImpl.java

    package hello.core.order;

    import hello.core.discount.DiscountPolicy;
    import hello.core.member.Member;
    import hello.core.member.MemberRepository;

    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은 FixDiscountPolicy를 의존하지 않는다!
    • DiscountPolicy 인터페이스만 의존한다.
    • 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다.
    • 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부(AppConfig)에서 결정한다.
    • 이제부터 실행에만 집중하면 된다.
    • MemoryMemberRepository, FixDiscountPolicy 객체의 의존관계가 주입된다.

     

     

     

     

     

    출처 : https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

     

    스프링 핵심 원리 - 기본편 - 인프런 | 강의

    스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런

    www.inflearn.com

Designed by Tistory.