본문 바로가기

Spring

Spring- Dependency Injection(의존 관계 주입)

DI(의존관계 주입)

 코드를 짜면서 내부 로직을 변경해야 하는 경우에 OCP 원칙이 잘 지켜지지 않을 수 있다. OCP 원칙과 DIP 원칙을 준수하기 위해서 DI를 진행한다.

 참고)

 OCP: 기존 코드를 변경하지 않는 것

 DIP: 클래스가 아닌 인터페이스에 의존하는 것

package hello.core.order;

import hello.core.discount.DiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;

public class OrderServiceImpl implements OrderService{
    //인터페이스에만 의존 (DIP 원칙 준수)
    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);
    }
}

 위 코드의 OrderServiceImpl은 인터페이스 OrderService를 구현한 클래스이다.

또한 내부 변수로는 MemberRepository memberRepositoryDiscountPolicy discountPolicy가 있다. 이 둘은 모두 인터페이스 변수이다. 클래스 참조 변수가 아닌 인터페이스 참조변수를 사용한 이유는 DIP 원칙을 준수하기 위함이다.

 

OrderServiceImpl에 사용된 인터페이스

package hello.core.member;

public interface MemberRepository {
    void save(Member member);
    Member findById(Long memberId);
}
package hello.core.discount;

import hello.core.member.Member;

public interface DiscountPolicy {
    // return이 할인 대상 금액
    int discount(Member member,int itemPrice);
}

 의존관계를 보면 OrderServiceImpl은 인터페이스에만 의존하고 있다. 즉 DIP 원칙이 준수되고 있다는 것이다.

그러나 지금  OrderServiceImpl의 생성자를 보면 외부로부터 생성자로 인자 값이 들어와야 한다는 사실을 확인 할 수 있다. 

 

 이러한 문제를 DI를 통해서 해결한다. 사실 별게 아니고 그냥 인자값 주입을 담당하는 클래스를 하나 만들면 된다.

의존관계 주입 담당 클래스 AppConfig

package hello.core;

import hello.core.discount.FixDiscountPolicy;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;

/**
 * Dip 원칙의 준수를 위해서 따로 생성한 AppConfig 클래스
 *
 * 생성자 주입을 통해서 service 클래스(OrderServiceImpll)은
 * 의존관계에 대한 고민을 더 이상 하지 않아도 되게 되었다.
 *
 * 더 이상 의존 관계 변경시에 클라이언트 코드를 변경하지 않아도 되고
 * AppConfig 내부 메소드의 인자만 변경해주면 되게 되었다.
 */

public class AppConfig {
    /**
     * AppConfig는 어플리케이션의 실제 동작에 필요한 구현 객체의 생성을 담당한다.
     * 생성한 객체의 인스턴스의 참조를 "생성자 주입"해준다.
     */

    public OrderService orderService(){
        //생성자 주입
        return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
    }
}

 주석을 잘 읽어보면 생성자 주입이라는 말이 자주 나오는데, 이는 그냥 대상 클래스(여기서는 OrderServiceImpl)의 생성자로 새로운 객체를 생성해서 그 참조를 인자로 넣어주는 것이다. 

 

 여기서는 MemoryMemberRepositoryFixDiscountPolicy 인스턴스를 생성해서 참조값을 넣어주고 있는데 이 둘은 각각 OrderServiceImpl에 있는 멤버  

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

하위 클래스이다.

 

MemberRepository의 하위클래스 MemoryMemberRepository

package hello.core.member;

import java.util.HashMap;
import java.util.Map;

public class MemoryMemberRepository implements MemberRepository{
    private static Map<Long, Member> store = new HashMap<>();

    @Override
    public void save(Member member) {
        store.put(member.getId(), member);
    }

    @Override
    public Member findById(Long memberId) {
        return store.get(memberId);
    }
}

DiscountPolicy의 하위클래스 FixDiscountPolicy

package hello.core.discount;

import hello.core.member.Grade;
import hello.core.member.Member;

public class FixDiscountPolicy implements DiscountPolicy{
    private final int discountFixAmount = 1000;
    @Override
    public int discount(Member member,int itemPrice) {
        if(member.getGrade() == Grade.VIP)
            return discountFixAmount;
        else
            return 0;
    }
}

 

DI의 결과

  현재 OrderServiceImpl은 어떠한 객체가 내부로 주입될지 알 수 없다.

AppConfig에서 생성자 주입을 담당한 결과, 어떠한 객체를 주입할 지는 오직 AppConfig에서 결정하게 되었다.

결과론적으로 OrderServiceImpl은 의존관계는 신경 쓸 필요 없이 오직 실행에만 집중하면 된다.