[spring]새로운 정책과 의존성 주입

정원석·2023년 12월 27일

김영한 강사님 강의를 들으며 공부한 내용 입니다.

새로운 할인 정책 개발

  • 악덕 기획자 : 서비스 할인 정책을 고정 금액 할인 에서 정률% 할인 정책으로 바꾸고 싶다. 예를들어 10%로 지정해두면 고객이 10000원 주문시 1000원 할인, 20000원 주문시 2000원 할인이다.

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

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 인터페이스 뿐만 아니라 FixDiscountPolicy인 구체 클래스도 함께 의존하고 있다.
->DIP위반

그래서 FixDiscountPolicy를 RateDiscountPolicy로 변경하는 순간 OrderServiceImpl의 소스코드도 함께 변경해야 한다.
-> OCP 위반

해결방안

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

인터페이스에만 의존하도록 코드 변경

public class OrderServiceImpl implements OrderService {
 //private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
   private DiscountPolicy discountPolicy;
}
  • 인터페이스에만 의존하도록 설계와 코드를 변경했다.
  • 그런데 구현체가 없는데 어떻게 코드를 실행시킬까?
  • 실행해보면 NPE(null point exception)이 발생한다.

->해결방안

  • 누군가 클라이언트인 OrderServiceImpl에 DiscountPolicy의 구현 객체를 대신 생성하고 주입해주어야 한다.

관심사의 분리 (역할분담)

  • 배우는 본인의 역할인 배역을 수행하는 것에만 집중한다.
  • 공연 기획자를 만들고, 배우와 공연 기획자의 책임을 확실히 분리하자.

AppConfig의 등장

  • 애플리케이션의 전체 동작 방식을 구성하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스를 만들자.

  • AppConfig는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.

-> MemberServiceImple
-> MemoryMemberRepository
-> OrderServiceImpl
-> FixDiscountPolicy

  • AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해 주입해준다.
    -> MemberServiceImpl -> MemoryMemberRepository
    -> OrderServiceImpl -> MemoryMemberRepository, FixDiscountPolicy

MemberServiceImpl - 생성자 주입

package hello.core.member;

public class MemberServiceImpl implements MemberService {

 private final MemberRepository memberRepository;
 
 public MemberServiceImpl(MemberRepository memberRepository) {
 
 this.memberRepository = memberRepository;
 }
 
 public void join(Member member) {
 
 memberRepository.save(member);
 
 }
 public Member findMember(Long memberId) {
 
 return memberRepository.findById(memberId);
 
 }
 
}
  • 설계 변경으로 MemberServiceImpl은 MemoryMemberRepository를 의존하지 않는다.
  • MemberRepository 인터페이스만 의존한다.
  • MemberSerbiceImpl 입장에서 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다.
  • MemberServiceImpl 의 생성자를 통해 어떤 구현 객체를 주입할지는 오직 왜부(AppConfig)에서 결정된다.
  • MemberServiceImpl은 이제부터 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중하면 된다.
  • 객체의 생성과 연결은 AppConfig가 담당한다.
  • DIP 완성 : MemberServiceImpl은 MemberRepository인 추상에만 의존하면 된다. 이제 구체 클래스를 몰라도 된다.
  • 관심사의 분리 : 객체를 생성하고 연결하는 역할과 실행하는 역할이 명확히 분리되었다.

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

appConfig는 memberService 객체를 만들 때 memoryMemberRepository도 생성한다. 그 다음 memberServiceImpl를 생성할 때 memoryMemberRepository의 참조값 "X001"을 같이 생성자에 넘긴다. -> memberServiceImpl은 생성한 memoryMemberRepository의 값을 주입받게 된다.

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

  • 처음으로 돌아가서 FixDiscountPolicy -> RateDiscountPolicy로 바꿔보자.
  • 어떤 부분만 바꾸면 되겠는가?

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

  • FixDiscountPolicy -> RateDiscountPolicy 로 변경해도 구성 영역만 영향을 받고, 사용 영역은 전혀 영향을 받지 않는다.
profile
Back-End-Dev

0개의 댓글