Java / Kotlin Class의 의존관계 주입(의존성 주입)

박준규·2023년 5월 7일
0

본 글은 김영한님의 스프링 핵심 원리 - 기본편을 본 이후에 작성되었습니다.

프로젝트의 할인 정책이 만약에 정액 할인에서 정률 할인으로 바뀐다면 어떻게 될까?

기존의 할인 정책인 고정 할인(FixDiscountPolicy)이 정률(RateDiscountPolicy)로 바뀌어야 한다.

Java

해당 클래스는 이미 구현이 완료된 상태이며, 주문시 작동해야 한다.

그렇다면, 어떻게 해야할까?

import hello.core.discount.DiscountPolicy;
import hello.core.member.data.Member;
import hello.core.member.repository.MemberRepository;
import hello.core.order.data.Order;
import hello.core.order.service.OrderService;

public class OrderServiceImpl implements OrderService {

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

    @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);
    }
}

기존의 OrderServiceImpl class를 보면, MemoryMemberRepositoryFixDiscountPolicy에 의존하고 있다.

이는 DIP(의존 역전 원칙)를 위반하고 있다. 따라서 RateDiscountPolicy인 정률 할인 클래스가 구현이 완료된다고 해도, OrderServiceImpl에 Field 주입을 진행해야 한다. 즉, OrderServiceImpl를 수정해야 하는 문제가 생겨버린다.

결국에는 처음부터 Achitecture를 잘못 설계해버려서 Order에 관여될 때 마다 관련된 모든 code를 수정해야 하는 번거로움이 생겨버린다.

그렇기 때문에, 지금 바로 class(구현체)가 아닌, interface 또는 abstract class에 의존할 수 있도록 DIP를 준수하도록 code를 수정해보자.

우선 Field로 구현체를 주입하고 있는 Code를 다음과 같이 interface에 의존하도록 바꾸어보자.

import hello.core.discount.DiscountPolicy;
import hello.core.member.data.Member;
import hello.core.member.repository.MemberRepository;
import hello.core.order.data.Order;
import hello.core.order.service.OrderService;

public class OrderServiceImpl implements OrderService {

    private MemberRepository memberRepository;
    private 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);
    }
}

이제 OrderServiceImpl class는 MemberRepository, DiscountPolicy interface에 의존하게 되었다.

위 코드는 interface 타입이 들어와 생성자를 생성하고 있으며, 해당 클래스를 주입해주면 된다.

어떻게 주입할 수 있을까?

답: 구현제 주입을 담당하는 class를 따로 만든다.

다음과 같이 AppConfig class를 만들어 구현제만을 관리하면 된다.

import hello.core.discount.DiscountPolicy;
import hello.core.discount.implement.FixDiscountPolicy;
import hello.core.member.repository.MemberRepository;
import hello.core.member.repository.MemberRepositoryImpl;
import hello.core.member.service.MemberService;
import hello.core.member.service.MemberServiceImpl;
import hello.core.order.implement.OrderServiceImpl;
import hello.core.order.service.OrderService;

public class AppConfig {


   /*...*/

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

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

    private DiscountPolicy disiscountPolicy() {
        return new FixDiscountPolicy();
    }
}

위 코드를 살펴보면, OrderService 타입인 OrderServiceImpl를 return할 때, memberRepository(), disiscountPolicy()를 주입하고 있으며, 해당 method는 각각의 구현체를 return 하고 있다.

이제 원래의 목적인 할인 정책의 변동이 찾아왔을 때, FixDiscountPolicy()를 RateDiscountPolicy()로 바꾼다면, 곧바로 적용되는 것을 확인할 수 있다.

이렇게 Field가 아닌 생성자를 통해 주입하는 방식을 생성자 의존관계 주입이라고 한다.

Kotlin

아래는 Kotlin으로 바꾸었을 때의 코드이다.

kotlin으로 생성자 주입을 진행했을 때는 다음과 같이 OrderServiceImpl를 만들어주면 된다.

import hello.corekotlin.discount.DiscountPolicy
import hello.corekotlin.member.repository.MemberRepository
import hello.corekotlin.order.data.Order
import hello.corekotlin.order.service.OrderService

class OrderServiceImpl(
    private val memberRepository: MemberRepository,
    private val discountPolicy: DiscountPolicy
): OrderService {

    override fun createOrder(memberId: Long, itemName: String, itemPrice: Int): Order {
        val member = memberRepository.findById(memberId)
        val discountPrice = discountPolicy.discount(member, itemPrice)

        return Order(
            memberId,
            itemName,
            itemPrice,
            discountPrice
        )
    }
}

위 코드를 java로 Decompile했을 때, 생성자가 자동으로 만들어진다.

또한 AppConfig는 다음과 같다.

import hello.corekotlin.discount.DiscountPolicy
import hello.corekotlin.discount.implement.RateDiscountPolicy
import hello.corekotlin.member.repository.MemberRepository
import hello.corekotlin.member.repository.MemoryMemberImpl
import hello.corekotlin.member.service.MemberService
import hello.corekotlin.member.service.MemberServiceImpl
import hello.corekotlin.order.implement.OrderServiceImpl
import hello.corekotlin.order.service.OrderService

class AppConfig {

    /*...*/

    fun orderService(): OrderService = OrderServiceImpl(
        memberRepositoy(),
        discountPolicy()
    )

    fun memberRepositoy(): MemberRepository = MemoryMemberImpl()

    fun discountPolicy(): DiscountPolicy = RateDiscountPolicy()

}
profile
'개발'은 '예술'이고 '서비스'는 '작품'이다

0개의 댓글