Spring 핵심 원리(2)

suhan cho·2022년 3월 14일
0

새로운 할인 정책 개발

  • 지금 처럼 고정 금액 할인이 아니라 좀 더 합리적인 주문 금액당 할인하는 정률% 할인으로 변경하고 싶다.
  • 예를 들어 10%로 지정해두면 고객이 10000원 주문시 1000원 할인, 20000원 주문시 2000원 할인

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

  • 새로 추가한 할인 정책을 변경하려면
public class OrderServiceImpl implements OrderService{
//  private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
 }

문제점

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

문제 해결 방법

  • 클라이언트 코드인 'OrderServiceImpl'은 DiscountPolicy의 인터페이스 뿐만 아니라 구체 클래스도 함께 의존한다.
  • 그래서 구체 클래스를 변경 시 클라이언트 코드도 함께 변경해야한다.
  • DIP위반하므로 추상에만 의존하도록 변경
private final MemberRepository memberRepository = new MemoryMemberRepository();
//    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
//    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
    private DiscountPolicy discountPolicy; //인터페이스만 보도록 했지만 이렇게하면 null포인트 에러 나온다. 

이 문제를 해결하기 위해서는 누군가가 클라이언트인 'OrderServiceImpl'에 'DiscountPolicy'의 구현 객체를 대신 생성하고 주입해주어야 한다.

관심사의 분리

  • 이전까지의 코드

    • 현재 까지는 연극으로 따지면 헨젤 역할, 그레텔 역할은 배우가 결정하는 사항이 아니다
    • 헨젤(인터페이스) 역할을 하는 홍길동(구현체)가 그레텔(인터페이스) 역할을 하는 홍길서(구현체)를 직접 뽑는 것과 같다.
    • 즉, 홍길동은 공연과 동시에 배우도 섭외해야한다.
  • 관심사의 분리

    • 배우는 자신의 본연의 역할만 하면 된다.
    • 홍길동은 어떤 여자 배우든 공연가능해야 한다.
    • 구상과 섭외는 별도의 기획자가 해야한다.
    • 기획자와 배우 확실히 나눠야 한다.

App Config 등장

애플리케이션의 전체 동작을 구성하기 위해, 구현 객체 생성하고 연결하는 별도의 설정 클래스

public class AppConfig {
    public MemberService memberService(){
       return new MemberServiceImpl(new MemoryMemberRepository());
    }

    public OrderService orderService(){
        return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
    }
}
  • App Config는 애플리케이션의 실제 동작에 필요한 구현 객체 생성
    • MemberServiceImpl
    • MemoryMemberRepository
    • OrderServiceImpl
    • FixDiscountPolicy
  • AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 '생성자를 통해서 주입(연결)' 해준다.
    • MemberServiceImpl -> MemoryMemberRepository
    • OrderServiceImpl -> MemoryMemberRepository, FixDiscountPolicy
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);
    }

}
  • 설계 변경으로 MemberServiceImpl은 MemoryMemberRepository를 의존하지 않는다.
  • MemberRepository 인터페이스만 의존
  • MemberServiceImpl 입장에서 생성자를 통해 어떤 구현 객체가 들어올지 알 수 없다.
  • MemberServiceImpl 의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부(AppConfig)에서 결정
  • MemberServiceImpl 은 이제부터 '의존관계에 대한 고민은 외부' 에 맡기고 '실행에만 집중' 하면 된다.

  • 객체의 생성과 연결은 'AppConfig'가 담당
  • DIP완성 : MemberServiceImpl은 MemberRepository인 추상에만 의존 이제 구체 클래스 몰라도 된다.
  • 관심사의 분리 : 객체를 생성하고 연결하는 역할과 실행하는 역할이 명확히 분리

  • appConfig 객체는 memoryMemberRepository 객체를 생성하고 그 참조값을 memberServiceImpl을 생성하면서 생성자로 전달
  • 클라이언트인 memberServiceImpl 입장에서 보면 의존관계를 마치 외부에서 주입해주는 것 같다고 해서 DI(Dependency Injection)의존성 주입이라 한다.

AppConfig 리팩토링

  • 메서드 명을 가져오는 순간 역할이 다 알 수 있게 한다.

  • new MemoryMemberRepsoitory() 중복을 제거해 이제 MemoryMemberRepository를 다른 구현체로 변경할 때 한 부분만 변경하면 된다.

  • AppConfig보면 역할과 구현 클래스가 한눈에 들어와 애플리케이션 전체 구성이 어떻게 되어있는지 빠르게 파악 가능

새로운 구조와 할인 정책 적용

  • FixDiscountPolicy-> RateDiscountPolicy

  • AppConfig의 등장으로 애플리케이션이 크게 사용 영역과, 객체를 생성하고 구성(Configuration)하는 영역으로 분리되었다

사용영역 손댈필요 없이 구성영역에서만 바꾸면 된다.

profile
안녕하세요

0개의 댓글