본문 바로가기
Study/Language

[Java] 제어의 역전(IoC)과 의존관계 주입(DI)

by jamiehun 2023. 3. 16.

김영한님의 Java 핵심원리를 다시 복습하며 제어의 역전과 의존관계 주입을 정리해본다.

 

결론부터 말하자면 

 

제어의 역전은 

프로그램의 제어흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것이다.

 

의존성 주입

실행시점(런타임)에 외부에서 실제 구현객체를 생성하고

클라이언트에 전달해서

클라이언트와 서버의 실제 의존관계가 연결되는 것

 

IoC컨테이너 혹은 DI 컨테이너

객체를 생성하고 관리하면서 의존관계를 연결해주는 것이다.

(아래에서는 AppConfig)

 

 

확 와닿는 말은 아닌 것 같다.

코드를 보면서 하나씩 정리해보자

 


1. 제어의 역전 (Inversion of Control)

1) 변경 전 : DIP가 지켜지지 않음!

주문서비스 클라이언트 (OrderServiceImpl)은 추상인터페이스 뿐만 아니라 구체(구현)클래스에도 의존하고 있다.

  • 추상 인터페이스 의존 : DiscountPolicy
  • 구체(구현) 클래스 의존 : FixDiscountPolicy, RateDiscountPolicy

할인정책 적용하기

OrderServiceImpl.java 변경 전

package hello.core.order;

public class OrderServiceImpl implements OrderService {
	// private final DiscountPolicy discountPolicy = new FixDiscountPolicy;
	private final DiscountPolicy discountPolicy = new RateDiscountPolicy;
    
    // 아래 생략
}

 

OrderService.java

package hello.core.order;

public interface OrderService {
    Order createOrder(Long memberId, String itemName, int itemPrice);
}

 

DiscountPolicy.java

package hello.core.discount;

import hello.core.member.Member;

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

 

RateDiscountPolicy.java

package hello.core.discount;

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

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

 

2) 변경후! IoC

OrderServiceImpl의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부 AppConfig에서 결정하며,

OrderServiceImpl 입장에서는 생성자를 통해 어떤 구현 객체가 들어올지 알 수 없다.

AppConfig.java

package hello.core;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.MemberRepository;
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;

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 FixDiscountPolicy();
        return new RateDiscountPolicy();
    }

}

 

OrderServiceImpl.java 변경 후

package hello.core.order;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;

public class OrderServiceImpl implements OrderService {
    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);
    }
}

 

정리하자면,

기존 프로그램은 클라이언트 구현 객체가 스스로 필요한 서버 구현객체를 생성, 연결, 실행하였다.

즉, 구현 객체가 프로그램의 제어 흐름을 스스로 조종하였음

 

반면에 AppConfig가 등장한 이후 구현 객체는 자신의 로직만 실행하는 역할을 담당하고,

프로그램의 제어 흐름은 AppConfig가 가져감

 

즉, 프로그램의 제어흐름 권한은 모두 AppConfig가 가지고 있으며,

    심지어 OrderServiceImpl 뿐만 아니라 OrderService의 구현객체들을 AppConfig가 실행할 수 있음

 

프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전 (Inversion of Control)이라고 함

 


2. 의존관계 주입 (Dependency Injection)

실행시점(런타임)에 외부에서 실제 구현객체를 생성하고

아래 그림과 코드에서 보면 OrderServiceImpl은 MemberRepository와 DiscountPolicy 인터페이스에 의존하고 있다.

정적인 의존관계(런타임과 반대되는)에서는 어플리케이션을 실행하지 않아도 의존관계를 알 수 있다.

하지만 아래의 코드 만으로는 OrderServiceImpl에 어떠한 구체적인 객체가 주입될지 알 수가 없다.

(인터페이스로만 선언이 되어 있기 때문)

 

 

OrderServiceImpl.java

package hello.core.order;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;

public class OrderServiceImpl implements OrderService {
    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);
    }
}

 

DiscountPolicy.java

package hello.core.discount;

import hello.core.member.Member;

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

 

FixDiscountPolicy.java

package hello.core.discount;

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

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;
        } else {
            return 0;
        }
    }

}

 

RateDiscountPolicy.java

package hello.core.discount;

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

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

 

동적인 객체 인스턴스 의존관계

AppConfig.java에서 볼 수 있듯 실행시점(런타임)에

OrderServiceImpl이 아닌 외부(AppConfig)에서 실제 구현 객체를 생성한다.

(MemberRepository는 MemoryMemberRepository를, DiscountPolicy는 RateDiscountPolicy를 생성)

 

클라이언트에 전달해서

여기서 클라이언트에 전달하는 과정이 발생한다.

AppConfig.java에서 구체적인 매개변수를 가지고 생성자를 생성한다.

생성자는 클라이언트(OrderServiceImpl.java)로 전달이 된다.

 

 

AppConfig.java

package hello.core;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.MemberRepository;
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;

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 FixDiscountPolicy();
        return new RateDiscountPolicy();
    }

}

 

OrderServiceImple.java

 

클라이언트와 서버 실제 의존관계가 연결

위의 과정을 거치고 나면 클라이언트는 인터페이스만 의존하더라도

외부에서 주입된 구현객체를 통해

객체지향이 추구하는 DIP (구체화가 아닌 추상화에 의존하는것)을 지킬 수 있다.

 

정리

정리하자면, 

의존관계 주입을 사용하면 정적인 클래스의 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다.

외부에서 객체의 인스턴스를 생성하고, 그 참조값을 전달해서 클라이언트와 서버가 실제로 의존관계로 연결되는 것이다.

의존성 주입을 통해 어플리케이션은 객체지향의 DIP 잘 지킬 수 있다.

 

* DIP : Dependency inversion Principle, 의존관계 역전원칙

 

 

[참고]

스프링핵심원리-기본편

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard