본문 바로가기

강의/스프링 핵심 원리 - 기본편

DI와 IoC를 활용하여 OCP, DIP를 지켜보자

개념


제어의 역전 (IoC)

제어의 역전이라는 개념은 프레임 워크같은 것이 대신 호출해주는 것입니다.

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

 

의존관계 주입 (DI)

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

코드로 확인하는 개념


아래와 같은 할인정책을 구현한다고 생각해보겠습니다.

 

보통 할인 정책을 구현하면 아래와 같이 코드를 작성할 것입니다.

 

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

 

public interface DiscountPolicy {

    /**
     * @return 할인 대상 금액
     */
    int discount(Member member, int price);
}

 

public class FixDiscountPolicy implements DiscountPolicy {

    private int discountFixAmount = 1000; // 1000원 할인
    @Override
    public int discount(Member member, int price) {
        if (member.getGrade() == Grade.VIP) {
            return discountFixAmount;
        }
        return 0;
    }
}

 

그러나 위와 같이 구현하면 한가지 문제점이 발생합니다.

OrderServiceImpl은 인터페이스인 DiscoutPolicy 뿐만 아니라 구현체 FixDisCountPolicy 까지 의존하고 있는 상황이 발생합니다.

 

 

이 문제를 해결하려면 누군가가 클라이언트인 OrderServiceImpl에 DiscountPolicy의 구현객체를 대신 생성해서 주입해 주어야 합니다.

 

아래와 같이 말이죠 (주석을 통해 변경 전 후 확인)

 

public class OrderServiceImpl implements OrderService {

//    private final MemberRepository memberRepository = new MemoryMemberRepository();
//    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private MemberRepository memberRepository;
    private 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);
    }
}

 

구성 영역은 config로 따로 빼내 줍니다.

 

public class AppConfig {

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

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

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

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

 

AppConfig의 등장으로 애플리케이션이 '사용 영역'과 '구성 영역' (Configuration)으로 분리되었습니다.

할인 정책을 변경해도 OrderServiceImple은 변함이 없으며, AppConfig의 구성 영역만 변경을 하면 됩니다.

DI 컨테이너


IoC 컨테이너, DI 컨테이너 AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을 IoC 컨테이너 또는 DI 컨테이너라 한다. 의존관계 주입에 초점을 맞추어 최근에는 주로 DI 컨테이너라 한다. 또는 어샘블러, 오브젝트 팩토리 등으로 불리기도 한다.

스프링 컨테이너


ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);

스프링 컨테이너 ApplicationContext 를 스프링 컨테이너라 합니다.

스프링 컨테이너는 AppConfig 파일에서 @Bean 이라 적힌 메서드를 모두 호출한 뒤 반환된 객체를 스프링 컨테이너에 등록합니다.

이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라 합니다.