Daily log

/* 과거 다른 블로그에 작성했던 내용입니다. */

 

 

 

최근 스프링 기초 개념부터 다시 차근차근 정리하는 중이다.

오늘은 제어의 역전 (IoC)의 개념을 좀더 명확히 하려고 한다.

제어의 역전을 왜할까?

제어의 역전은 객체나 메서드의 호출을 개발자가 직접 결정하는게 아니라, 외부에서 결정하는 것을 의미한다.

다른 블로그를 찾아보면 프레임워크와 라이브러리의 차이를 예로 들어 설명하거나, 바로 위의 설명처럼 개발자가 아닌 스프링 컨테이너(외부)가 결정한다고 설명한다.

 

하지만 나는 왜? 라는 의문이 계속 생겨 좀처럼 와닿지가 않았다.

'그래서 왜 외부에서 결정하는건데...??'

김영한님의  [스프링 핵심 원리 - 기본편]의 강의 중 [IoC, DI, 그리고 컨테이너] 영상을 보아도 '왜?' 라는 의문이 사라지지 않았는데, 같은 강의 초반 부분 [좋은 객체 지향 설계의 5가지 원칙(SOLID)]를 들으며 의문이 해소가 되었다.

 

 한마디로 좋은 객체지향 프로그래밍을 하기 위해서다.

 

OCP 개발-폐쇄 원칙과 DIP 의존관계 역전 원칙

SOLID 중 O는 OCP 개발-폐쇄 원칙이고, D는 DIP 의존관계 역전 원칙이다.

OCP에서 가장 중요한 요소는 "소프트 웨어는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다." 이고,

DIP에서 가장 중요한 요소는 "프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다." 이다. 

여기서부터 아래 소스를 확인해 보자.

private final DiscountPolicy discountPolicy = new FixDiscountPolicy(); // 고정 할인 정책

위는 OrderService에서 할인 정책을 가져오기 위해 생성한 객체이다. DiscountPolicy라는 인터페이스를 FixDiscountPpolicy라는 구현체로 생성하였는데, 갑자기 고객사에서 정률 할인 정책으로 변경을 요청했다.

아래 소스를 보자.

//private final DiscountPolicy discountPolicy = new FixDiscountPolicy();    // 고정 할인 정책
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();   // 정률 할인 정책

고객의 요구대로 고정 할인 정책에서 정률 할인  정책으로 변경하였는데, 여기서 OCP 원칙을 위반했고,

처음부터 DIP 원칙을 위반했다.

 

 1. OCP 원칙 위반

우리는 고객의 요구를 들어주기 위해 비즈니스 로직 OrderService를 수정하였다. 

 2. DIP 원칙 위반

DiscountPolicy라는 인터페이스만 의존해야 하지만, FixDiscountPolicy나 RateDiscountPolicy도 의존하고 있다.

 

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

여기서 제어의 역전 의존성 주입이 나온다.

 

제어의 역전 (IoC)과 의존성 주입 (DI)

처음에 설명한대로 제어의 역전이란 개발자가 제어하지 않고 외부에서 제어하는 것이었다. 

왜? OCP 원칙을 위반하지 하지 않기 위해. 즉, 비즈니스 로직을 변경하지 않기 위해서. 

의존성 주입은 제어의 역전을 위해 필요한 방법인데, 비즈니스 로직을 변경하지 않기 위해서 해당 서비스의 생성자를 통해 의존성을 주입한다. 아래 소스를 보자.

//    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();    // 고정 할인 정책
//    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();   // 정률 할인 정책

    private final DiscountPolicy discountPolicy;

    //생성자를 통해서 주입
    public OrderServiceImpl(DiscountPolicy discountPolicy) {
        this.discountPolicy = discountPolicy;
    }
@Configuration
public class AppConfig {

    @Bean 	// Bean => 스프링 컨테이너에 등록이 된다.
    public OrderService orderService() {
        return new OrderServiceImpl(discountPolicy());
    }

    @Bean
    public DiscountPolicy discountPolicy() {
        //return new FixDiscountPolicy();       //  20,000 -> 1000할인 (고정)
        return new RateDiscountPolicy();        //  20,000 -> 2000할인 (10%)
    }
}

 첫번째 코드블럭을 보면 기존에 생성과 초기화를 같이 했던 discountPolicy는 final 키워드로 선언만 되어있고, 값은 생성자를 통해서 주입받는다. 현재 DiscountPolicy라는 인터페이스만 의존하기 때문에 DIP 원칙에 위반되지 않는다.

 두번째 코드블럭을 보면 AppConfig라는 클래스를 @Configuration 어노테이션으로 지정하고, 그안에 OrderService와 DiscountPolicy 두 인터페이스를 @Bean 어노테이션으로 스프링 컨테이너에 올렸다. 이렇게 하면 스프링이 실행되면서 컴포넌트를 스캔하여 두 인터페이스는 스프링 컨테이너에서 관리가 되기 때문에 제어의 역전이 발생한다. 또한 고정 할인 FixDiscountpolicy에서 정률 할인 RateDiscountPolicy로 변경되어도 비즈니스 로직은 변경되지 않고 추가되는 구현체만 확장하여 적용하면 되기 때문에 OCP 원칙에도 위반되지 않는다.

 

끝으로...

오늘은 제어의 역전에 대해 개념을 명확히 하려고 했다.

하지만 왜?라는 의문 하나 때문에 몇 시간동안 IoC를 포함하여 OCP, DIP, DI 등 다양한 공부를 했다.

 

최근까지만 해도 스프링 기본 개념에 대해서는 어렴풋이... 아니 거의 모르고 스프링 부트나 JPA 개발에 대한 욕심만 많았는데, 개발은 개발대로 하고 이렇게 하나하나 스프링 기초부터 정리를 하니까 더 자신감이 생기는거 같아서 기분이 좋다.

다음에는 어떤 개념을 공부해볼까...?

공유하기

facebook twitter kakaoTalk kakaostory naver band