Spring DI/IoC란?

mark1106·2024년 3월 9일
0

스프링

목록 보기
1/5
post-thumbnail

DI란?

DI(Dependency Injection)는 객체 간의 의존성을 외부에서 주입해주는 디자인 패턴

IoC란?

IoC(Inversion Of Control, 제어의 역전)는 간단히 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것

이 말만 듣고 이해하기 어려우니 밑에 풀어서 설명하려 한다.

DI와 IoC의 등장 배경

이해를 돕기 위해 위의 클래스 다이어그램을 참고하여 각 클래스의 상세 기능 구현보다는 코드의 핵심 부분을 설명하려 한다.

public class OrderServiceImpl implements OrderService {

    private DiscountPolicy discountPolicy = new FixDiscountPolicy();

}
  1. OrderServiceImpl에서 할인 정책을 FixDiscountPolicy(고정 할인 정책) 사용하고 있다고 가정해보자.
  2. 할인 정책이 FixDiscountPolicy -> RateDiscountPolicy(비율 할인 정책)으로 변경되었다.
  3. 이 상황을 대비하여 DiscountPolicy 인터페이스를 구현한 RateDiscountPolicy 구현 클래스를 적용하였다.
public class OrderServiceImpl implements OrderService {

    private DiscountPolicy discountPolicy = new RateDiscountPolicy();

}

하지만 이는 객체 지향 설계 원칙(SOLID)에 위배된다

1. SRP(단일 책임 원칙) : 한 클래스는 하나의 책임만 가져야 한다.

public class OrderServiceImpl implements OrderService {

   private DiscountPolicy discountPolicy = new FixDiscountPolicy();

}

위 코드는 OrderServiceImpl(클라이언트)가 본인의 로직 외에 의존 관계까지 책임지고 있다.

2. OCP(개방-폐쇄 원칙) : 코드 확장에는 열려 있고 변경에는 닫혀 있어야 한다.

public class OrderServiceImpl implements OrderService {

//    private DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private DiscountPolicy discountPolicy = new RateDiscountPolicy();

}

FixDiscountPolicy에서 RateDiscountPolicy로 새로운 요구사항에 맞춰 기능은 확장 되었지만 그와 동시에 OrderServiceImpl의 코드 또한 바뀌고 있다.


3. DIP(의존관계 역전 원칙) : 추상화(interface)에 의존해야 하며, 구체화에 의존하면 안된다.

public class OrderServiceImpl implements OrderService {

   private DiscountPolicy discountPolicy = new FixDiscountPolicy();

}

DiscountPolicy라는 인터페이스에 의존해야 하는데 위 코드는 FixDiscountPolicy라는 구현체에 의존하고 있다.

정리하자면 클래스가 많은 책임을 지고(SRP), 코드 변경이 발생하고(OCP), 클라이언트가 구체화에 의존(DIP)하므로 객체지향 설계 원칙을 지킬 수 없다.

그렇다면 이 문제를 어떻게 해결할까?
-> 인터페이스에만 의존하도록 설계해야 한다.

public class OrderServiceImpl implements OrderService{

    private DiscountPolicy discountPolicy;
}

이렇게 FixDiscountPolicy라는 구현체를 직접 참조하는 것이 아닌 FixDiscountPolicy의 인터페이스인 DiscountPolicy만 의존하는 것이다.

하지만 이들은 인터페이스이므로 이들을 작동 시켜줄 구현체가 필요하다.

그렇기 위해서는 누군가 OrderServiceImpl에 DiscountPolicy 구현 객체를 생성하고 주입해줘야 한다.

이러한 상황 때문에 DI(Dependency Injection) 개념이 등장했다.

관심사의 분리(AppConfig)

별도의 설정 클래스 AppConfig를 만들어 보자.

AppConfig는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다. 이 때 생성자를 통해서 주입을 해주자.

인터페이스만 의존하는 OrderServiceImpl

public class OrderServiceImpl implements OrderService {

    private DiscountPolicy discountPolicy;

    public OrderServiceImpl(DiscountPolicy discountPolicy) {
        this.discountPolicy = discountPolicy;
    }

}

설정 클래스 AppConfig

public class AppConfig {

    public OrderService orderService() {
        return new OrderServiceImpl(new FixDiscountPolicy());
    }

}

위와 같은 구현은 AppConfig라는 관리자(외부)가 어떤 구현체를 사용할 것인가에 대한 모든 권한을 가지고, OrderServiceImpl은 AppConfig가 선택한 구현체(FixDiscountPolicy)를 주입만 받아 실행만 하면 된다.

즉, 객체를 생성하고 연결하는 역할(AppConfig)과 실행하는 역할(OrderServiceImpl)이 명확히 분리되었다.

외부(AppConfig)에서 객체 간의 의존성(OrderServiceImpl -> DiscountPolicy)을 주입하는 디자인 패턴이 바로 DI이다.

사용 영역과 구성 영역의 분리로 인해 OrderServiceImple은 구현체가 아닌 인터페이스인 DiscountPolicy만 의존하는 것을 볼 수 있다.

잠깐 위에서 설명을 미뤄뒀던 IoC(제어의 역전)에 대해 떠올려보자.

IoC는 제어 흐름을 직접 제어하는 것이 아닌 외부에서 관리하는 것이라 하였다.

위 그림을 보면 OrderServiceImpl에서 사용하는 할인 정책을 FixDiscount를 사용할지, RateDiscount를 사용할지는 AppConfig에서 설정한다.

따라서 AppConfig(외부)가 OrderServiceImpl의 흐름을 결정한다.

즉 IoC, 제어의 역전이 일어났다는 것을 확인할 수 있다.

IoC와 DI의 차이

IoC와 DI가 헷갈려서 찾아보았다.
IoC는 제어가 역전 된다는 개념이며, IoC의 한 방법 중에 DI(의존성 주입)가 있다.

그리고 Spring Framework의 IoC 기능의 대표 동작원리가 바로 DI이다.

즉, 우리가 앞으로 사용할 Spring 컨테이너는 DI 컨테이너라고 부르는 것이 맞다.

위 내용을 통해 DI와 IoC의 장점을 살펴보자.

IoC의 장점

  • 객체 간 낮은 결합도
  • 유연한 코드 작성 가능
  • 가독성 증가
  • 코드 중복 방지
  • 유지 보수 용이

DI의 장점

  • 객체 간의 결합도, 의존성이 줄어든다.-
  • 코드의 재활용성이 높아진다.
  • 가독성이 높아진다.

📚 참고

profile
뒤돌아보면 남는 것은 사진, 그리고 기록 뿐

0개의 댓글