[Spring] 스프링 핵심 원리의 이해 - 예제 개선하기

DEINGVELOP·2023년 4월 11일
0

0. 프로젝트 기획 변경

새로운 할인 정책 개발

  • 악덕 기획자 : 고정 금액 할인이 아닌, 10% 정률 할인으로 변경하고자 한다.

📣 참고 : 애자일 소프트웨어 개발 선언

0-1. 설계

RateDiscountPolicy

0-2. 설계 변경에서 발견한 문제들!

  • 현재의 코드는 OCP, DIP를 위반하고 있다.
public class OrderServiceImpl implements OrderService {
    private final MemberRepository memberRepository = new MemoryMemberRepository();
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
    // ↑ 변경 전
    // OCP 위반! - 결국 코드를 변경해야 하기 때문
    // DIP 위반! - 추상(인터페이스)에만 의존해야 하는데 구현(클래스)에도 의존하고 있기 때문
    
    // 그러면 이렇게 바꾸면?
    private DiscountPolicy discountPolicy;
    // => 당연히 NPE 발생!

	...
    }
}

1. 관심사의 분리

  • 배우는 본인의 배역을 수행하는 것에만 집중해야 한다.
  • 남배우는 어떤 여자 주인공이 되더라도 똑같이 공연할 수 있어야 한다.
  • 공연을 구성하고, 담당 배우를 섭외하고, 역할에 맞는 배우를 지정하는 공연 기획자가 필요하다.

1-1. AppConfig = 공연기획자

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

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

  • 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해 주입(연결)한다.

1-2. 이제는 DIP, OCP를 지키는 것이다.

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);
    }
}
  • MemberServiceImpl 입장에서 이제는 생성자를 통해 어떤 구현 객체가 들어올지는 알 수 없다.
  • 어떤 구현 객체를 주입할지는 오직 외부(AppConfig)에서 결정한다.
  • 이제 의존 관계에 대한 고민은 외부에 맡기고, 담당 배우들은 기능을 실행하는 책임만 지면 된다!

1-3. 변경된 클래스 다이어그램

  • 클라이언트인 memberServiceImpl 입장에서 보면, 의존관계를 마치 외부에서 주입해주는 것 같다고 해서 DI(Dependency Injection), 우리 말로 의존관계 주입 또는 의존성 주입이라고 한다.

2. 리팩터링

2-1. AppConfig 리팩토링

변경 후

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

    private MemoryMemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }
}

장점

  • 나중에 DB를 바꾸고 싶더라도, 이 AppConfig만 변경하면 됨!
  • AppConfig를 통해, 역할과 구현 클래스가 한 눈에 들어옴. 또, 애플리케이션 전체 구성이 어떻게 되어 있는지 빠르게 파악할 수 있음

  • 즉, 변경이 생기더라도 구성 영역만 손대면 되고, 사용 영역의 어떠한 코드도 변경할 필요가 없음!
  • 구성 영역은 요구사항의 변경에 따라 당연히 변경됨!

0개의 댓글