DI와 IoC

taeheon95·2022년 9월 3일
0

spring

목록 보기
1/2

DI

DI(Dependency Injection)란 스프링이 다른 프레임워크와 차별화되어 제공하는 의존 관계 주입 기능으로, 객체를 직접 생성하는 게 아닐 외부에서 생성한 후 주입 시켜주는 방식이다.

스프링은 DI(의존성 주입)을 통해 모듈 간의 결합도를 낮추고 유연성을 높인다.

IoC(Inversion of Control)란 “제어의 역전”이라는 의미로, 메소드나 객체의 호출 작업을 개발자가 아닌 외부에서 제어하는 것으로 의존성을 역전시켜 객체 간의 결합도를 줄이고 유연한 코드를 작성할 수 있도록 하여 가독성을 좋게 하고 코드 중복, 유지 보수등을 편하게 할 수 있게 한다.

DI와 IoC는 두개가 상충되고 다른 개념이 아니라 스프링 프레임워크에서는 IoC를 DI, 그리고 다른 개념으로 실현 시키고 있다.

DI의 필요성

그럼 스프링 DI는 어떤 역할을 하는 것이고 어떻게 동작을 하는 것일까?

먼저 세가지 클래스/인터페이스 코드를 보자

TestService 인터페이스

public interface TestService {
    void test1();
}

TestServiceImpl1 클래스

public class TestServiceImpl1 implements TestService {
    @Override
    public void test1() {
        System.out.println("TestServiceImpl1.test1");
    }
}

TestController 클래스

public class TestController {
    private final TestService testService;

    public TestController() {
        this.testService = new TestServiceImpl1();
    }
}

만약에 우리가 스프링의 도움 없이 서비스와 컨트롤러를 구현한다면 처음 모양은 이 모양이 되게 될 것이다. 그런데 이 코드는 한가지의 문제점이 있다. TestController에서 구현에 의존하는 것을 피하기 위해서 우리는 TestService라는 인터페이스를 이용하여 구현을 하였다. 그러나 TestController는 실제 우리가 구현한 인터페이스가 아닌 인터페이스를 구현한 TestServiceImpl1 객체에 의존하게 되는 일이 발생한다.

이는 우리가 인터페이스를 구현하는 궁국적인 이유인 개방성이 결국 실제 구현에 의존하게 됨으로 인터페이스가 아닌 실제 객체에 의존하게 되어서 solid 원칙중 하나인 개방-폐쇄 원칙을 어기게 되는 일이 일어나게 된다.

실제로 요구사항이 변화하여 TestServiceImpl1객체가 아닌 다른 TestService를 구현한 객체에 의존을 하게 된다면 그 요구사항이 변화할 때마다 우리는 TestController의 코드를 변경해야 하는 일이 생긴다.

TestServiceImpl2 클래스

public class TestServiceImpl2 implements TestService {
    @Override
    public void test1() {
        System.out.println("TestServiceImpl2.test1");
    }
}

TestController 클래스

public class TestController {
    private final TestService testService;

    public TestController() {
        this.testService = new TestServiceImpl2();
    }

    public void test1() {
        testService.test1();
    }
}

이렇게 우리는 TestService 객체의 구현체를 다른 구현체로 사용하기로 바꿀 때마다 TestController 객체를 변경해야하는 일이 생기게 된다.

해결책

그럼 이런 구현체의 추가또는 변화에 따른 의존성 문제를 해결하기 위해선 어떤 방식을 이용해야 할까?

해결 방법은 두단계로 나누어 진다.

  1. TestController 에서 TestService 객체를 생성자를 외부에서 주입 받게 함으로써 외부에서 의존성을 관리하게 만들어준다.
public class TestController {
    private final TestService testService;

    public TestController(TestService testService) {
        this.testService = testService;
    }

    public void test1() {
        testService.test1();
    }
}
  1. 외부에서 의존성을 주입한다.
public class TestConfig {

    private TestService testService;
    private TestController testController;

    public TestService testService() {
        if(testService == null) {
            testService = new TestServiceImpl1();
        }
        return testService;
    }

    public TestController testController() {
        if(testController == null) {
            testController = new TestController(testService());
        }
        return testController;
    }
}

이렇게 TestConfig 라는 클래스를 만들어서 의존성을 관리하게 되면 testService 의 구체적인 클래스가 변화하게 될 때,

public class TestConfig {

    private TestService testService;
    private TestController testController;

    public TestService testService() {
        if(testService == null) {
            testService = new TestServiceImpl2();
        }
        return testService;
    }

    public TestController testController() {
        if(testController == null) {
            testController = new TestController(testService());
        }
        return testController;
    }
}

이 방법은 외부에서 변경을 해도 되는 객체를 만들어서 그 객체의 변화로 다른 객체들이 구체성에 의존하고 변경에 의존해야 하는 코드들을 관리함으로 인해서 다른 객체들의 개방 패쇄 원칙을 지키는 것으로 TestConfig 객체는 변화하게 되지만 TestController의 코드는 변화가 없이 의존성을 관리하고 개방 폐쇄 원칙을 지킬 수 있게 된다.

스프링은 위의 Config 객체에서 함수로 객체를 외부에서 등록한 것 처럼 @Service, @Controller, @Repository, @Component어노테이션으로 객체를 등록하고 등록된 객체가 의존하는 외부의 객체를 스프링이 주입해줌으로 의존성 주입을 하게 된다.

IoC(제어의 역전 : Inversion of Control)

제어의 역전은 프로그래머가 작성한 프로그램이 재사용 라이브러리의 흐름 제어를 받게 되는 소프트웨어 디자인 패턴을 말한다. 제어의 역전이 적용된 프로그래밍은 프로그래머가 작성한 프로그램이 외부 라이브러리 또는 프레임워크의 코드를 호출해서 이용하는 것이 아니라 외부 프레임워크 또는 라이브러리의 코드가 프로그래머가 작성한 코드를 호출한다.

스프링에선 바로 위의 해결책을 통해 실제 객체가 의존해야하는 객체들을 스프링이 대신 DI를 시켜줌으로 인해 제어의 역전을 구현한다.

예전에는 XML을 통해 제어의 역전을 구현하였고 현재는 @Service, @Controller, @Repository, @Component어노테이션으로 빈으로 만들 수 있게하고 @ComponentScan 으로 사용자가 빈으로 만든 클래스를 스프링 컨테이너에 등록을 함으로 스프링이 코드를 대신 주입해서 동작할 수 있게 한다.

DI와 IoC의 장점

  1. 객체 간에 약한 결합을 하게하여 객체 간의 의존 관계를 쉽게 변경할 수 있다.
  2. 객체가 의존하고 있는 외부 객체를 상세 객체가 아닌 추상화 된 객체를 사용할 수 있게 함으로 인해서 객체의 재사용성을 높인다.

0개의 댓글

관련 채용 정보