Java Spring Core

New Discount Policy

기존의 할인 정책에 새로운 할인 정책을 추가해서 이를 적용해보자

RateDiscountPolicy

public class RateDiscountPolicy implements DiscountPolicy{
    private int discountPercent=10;
    @Override
    public int discount(Member member, int price) {
        if(member.getGrade()== Grade.VIP){
            return price*discountPercent/100;
        }else{
            return 0;
        }
    }

VIP 인 회원들에게 10%의 할인율을 적용하는 것이다.

DiscountPolicy의 구현체가 바뀌었으니, 해당 부분도 수정해줘야한다.

 /*private DiscountPolicy discountPolicy = new FixDiscountPolicy();
 */
    private DiscountPolicy discountPolicy = new RateDiscountPolicy();

이렇게 하면 새로운 할인정책이 적용된다.

OCP/DIP Violation

하지만 지금까지 구현해온 것처럼 진행하면 DIP/OCP 원칙을 위배하게 된다.

새로운 할인정책을 적용하려면 위와 같이 OrderServiceImpl 클래스에서 직접 구현클래스를 변경해줘야한다. 이는, 역할에만 의존적이어야 하는 DIP 원칙을 위배하는 것이다. 또한, 해당 부분을 수정함으로써, 서비스 코드에 대한 수정이 발생하게 된다. 이는 OCP의 원칙을 위배하게 되는 것이다.

그러면 DIP 원칙을 지키려면 어떻게 해야될까? DIP를 항상 지키위해서는 추상화에 의존하는 코드를 작성해야한다.

Refactor

private DiscountPolicy discountPolicy;

위 처럼, 오직 interface에만 의존적인 코드를 작성하고, 서비스 코드에서는 이 interface를 통한 비지니스 로직을 구현해야 한다. 하지만, 이렇게 하려면 누군가는 대신해서 구현클래스를 생성하고 이를 interface에 할당해줘야한다.

AppConfig

AppConfig는 위의 문제를 해결해준다.

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

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

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

private DiscountPolicy discountPolicy() {
    /*return new FixDiscountPolicy();*/
    return new RateDiscountPolicy();
} }

AppConfig에서는 위와 같이 각각의 interface에 대한 구현클래스를 생성해주며, 필요한 구현클래스들을 생성자들을 통해 연결해준다. 이렇게 구현클래스들을 연결해주는 작업을 DI(Depedency Injection)이라고 한다. DI 설정을 위해 각각의 구현클래스들에 대한 생성자 함수를 만들어 줘야한다.

>MemberService

```java
//기존에 구현클래스를 직접 할당하던 방식에서 interface만 생성해준다.
//private final MemberRepository memberRepository=new MemoryMemberRepository();
private final MemberRepository memberRepository;
    public MemberServiceImpl(MemberRepository memberRepository){
        this.memberRepository=memberRepository;
    }

OrderService

/*
private DiscountPolicy discountPolicy = new FixDiscountPolicy();
private DiscountPolicy discountPolicy = new RateDiscountPolicy();
 */
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;

public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}

이렇게 하면 각각의 interface들은 구현 클래스들을 입력받을 준비가 된것이다. MemberServiceImpl는 오직 MemberRepository interface에만 의존하며, 어떠한 구현 클래스가 내부에서 동작하는지에 대한 정보는 알지 못한다. OrderServiceImpl 또한 마찬가지로, MemberRepository, DiscountPolicy interface에만 의존한다.

junit Test

MemberServiceTest

MemberService memberService;
//테스트를 진행하기 전에 AppConfig을 통한 DI 진행
@BeforeEach
public void beforeEach(){
    AppConfig appConfig=new AppConfig();
    memberService= appConfig.memberService();
}

@Test
void join(){
    //given
    Member member = new Member(1L,"memberA", Grade.VIP);

    //when
    memberService.join(member);
    Member findMember=memberService.fingById(1L);
    
    //then
    Assertions.assertThat(member).isEqualTo(findMember);
}

OrderService Test

MemberService memberService;
OrderService orderService;

@BeforeEach
public void beforeEach(){
    AppConfig appConfig=new AppConfig();
    memberService= appConfig.memberService();
    orderService= appConfig.orderService();
}
@Test
void createOrder(){
    long memberId=1L;
    Member member=new Member(memberId,"memberA", Grade.VIP);
    memberService.join(member);

    Order order=orderService.createOrder(memberId, "itemA",10000);
    Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
    Assertions.assertThat(order.calculatePrice()).isEqualTo(9000);
}

이렇듯 AppConfig에서는 객체의 생성 및 의존성 연결 역할을 담당하며 Client Code(Member Service, Order Service)에서는 오직 실행의 역할만을 수행한다.

IoC, DI

IoC

IoC 제어의 역전(Inversion on Control): 클라이언트 코드인 (MemberServiceImpl, OrderServiceImpl)은 오직 자신의 로직/코드를 실행하는 역할만을 수행한다. 어떤 클래스가 구현 클래스로 오는 지를 알지 못한 상태로 자신의 역할만을 수행한다. 이 처럼, 프로그램의 제어 흐름을 자기 자신이 아니라 외부에서 통제하는 것을 제어의 역전이라고 한다. AppConfig 처럼 필요한 구현클래스들을 외부에서 정해주면, 서비스 코드는 그냥 받은대로 실행하기만 하면 된다.

DI

DI 의존성 주입(Dependency Injection): 구현객체를 클라이언트에게 전달해서 이를 실제로 연결하는 작업을 DI라고 한다.

의존관계에는 크게 정적의존,동적의존이 있는데,

class_diagram

정적의존은 위 그림에서 클래스 다이어그램에 표현된 의존 관계를 뜻한다. 어플리케이션을 실행하지 않더라도 알 수 있는 의존관계이다. OrderService는 MemberRepository, DiscountPolicy와 의존관계다라는 것을 파악할 수 있다.

하지만 OrderService가 MemoryMemberRepository 나 DBMemberRepository 연결될지, FixDiscountPolicy, RateDiscountPolicy에 연결될지는 실행하지 않고는 모르는 것이다.

이처럼 실제 실행을 통해 의존관계가 연결되는 것을 동적인 의존관계라고 하며, 이는 객체 다이어그램의 의존 관계를 통해 알 수 있다.

동적 의존 관계 형성을 하는 것이 바로 DI이다.

AppConfig 처럼 실제로 객체를 생성하고 연결해주는 작업을 진행하는 것이 DI 컨테이너이다. 이는 Spring에서 기본적으로 제공해주는 기능이다.

References

link: inflearn

link:springcore

댓글남기기