Home [Spring] 이해를 위한 예제
Post
Cancel

[Spring] 이해를 위한 예제

비즈니스 요구사항


  • 회원
    • 회원을 가입하고 조회할 수 있다.
    • 회원은 일반과 VIP 등급이 있다.
    • 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다.

회원 클래스 다이어그램

Untitled.png

회원 객체 다이어그램

Untitled1.png

  • 주문
    • 회원은 상품을 주문할 수 있다.
    • 회원 등급에 따라 할인 정책을 적용할 수 있다.
    • 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용한다.(나중에 변경 가능)

주문 클래스 다이어그램

Untitled2.png 주문

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class OrderServcieImpl implements OrderService {

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

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    public OrderServcieImpl(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 discount = discountPolicy.discount(member, itemPrice);
        return new Order(memberId, itemName, itemPrice, discount);
    }
}

주문 객체 다이어그램

Untitled3.png

만약, 여기서 할인 정책이 고정 정책 할인이 아니라 비율에 따른 정책으로 바꾸게 된다면 OrderServiceImpl의 코드는 다음과 같이 수정된다.

OrderServiceImpl

1
2
3
public class OrderServiceImpl implements OrderService {
    //private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
		private final DiscountPolicy discountPolicy = new RateDiscountPolicy();

OrderServiceImpl은 지금 인터페이스(DiscountPolicy)를 의존하면서 동시에 구현 클래스(RateDiscountPolicy)도 의존하고 있기 때문에 DIP(Dependency Inersion Principle)를 지키지 못했고, RateDiscountDisplay의 기능을 추가하면 기존의 코드를 수정해야 하므로 OCP(Open Closed Principle)에도 위배되는 것이다.

기대했던 의존 관계

Untitled4.png

실제 의존 관계 다이어그램

Untitled5.png

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

관심사의 분리


어플리케이션을 하나의 공연이라 생각해보자. 각각의 인터페이스를 배역(배우 역할)이라 하면 실제 배역을 맞는 배우를 선택하는 것은 누가 하는가?

앞서 코드에서는 배우가 직접 상대역의 배역과 배우를 직접 고르는것과 같다. 마치 로미오 역할(인터페이스)을 하는 레오나르도 디카프리오(구현체, 배우)가 줄리엣(인터페이스)을 하는 여자 주인공(구현체, 배우)을 직접 초빙하는 것과 같은 것이다. 이처럼 디카프리오는 공연도 해야하고, 여자 주인공을 직접 초빙해야 한다는 다양한 책임을 갖고 있는 것이다.(SRP 위반)

이처럼, 배우는 본인의 역할인 배역에만 집중해야 하며, 배역에 맞는 배우를 캐스팅 하는것은 공연 기획자가 담당해야 한다. 공연 기획자를 만들고 배우와 공연 기획자의 책임을 분리해야 한다.

AppConfig

어플리케이션의 전체 동작 방식을 구성하고, 구현 객체를 생성하고, 연결하는 책임을 갖는 클래스이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class AppConfig {

    public MemberService memberService() {
        return new MemberServiceImpl(getMemberRepository());
    }

    private static MemoryMemberRepository getMemberRepository() {
        return new MemoryMemberRepository();
    }

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

    public DiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }
}
  1. AppConfig는 어플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.
  2. AppConfig는 생성한 객체 인스턴스의 레퍼런스를 생성자를 통해서 주입해준다.

OrderServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class OrderServcieImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    public OrderServcieImpl(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 discount = discountPolicy.discount(member, itemPrice);
        return new Order(memberId, itemName, itemPrice, discount);
    }
}

이렇게 되면 OrderServiceImpl 코드는 구현제에 직접 의존하지 않고 생성자를 통해 Appconfig가 구현체를 주입해주게 된다.

IOC, DI 컨테이너


IOC(Inversion of Control)

  • 기존에 클라이언트에서 직접 객체를 생성해서 연결하고 실행한 것은 구현 객체가 스스로 프로그램의 제어 흐름을 스스로 조종한 것이다.
  • AppConfig 클래스를 생성한 후 객체는 자신이 담당하는 로직만 실행할 수 있게 되었다. 프로그램의 제어 흐름은 AppConfig에서 담당하였다.(공연 기획자)
  • AppConfig는 OrderServiceImpl이 아닌 OrderService 인터페이스의 다른 구현 객체를 생성하고 실행할 수도 있다.

이렇게 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IOC)라고 한다.

프레임워크 vs 라이브러리

  • 프레임워크는 내가 작성한 코드를 제어하고, 실행의 흐름을 제어해준다면 프레임워크(JUnit)
  • 라이브러리는 내가 작성한 코드가 직접 제어의 흐름을 담당하면 라이브러리

DI(Dependency Inversion)

  • 애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결 되는 것을 의존관계 주입이라 한다.