[Spring] IoC & DIP 그리고 DI

이준영·2023년 8월 26일
1

🌱 Spring

목록 보기
9/18
post-thumbnail

IoC DI에 대해 수 없이 들어보고 개념도 어느 정도 알고 있기는 한데 다른 사람에게 정확하게 이런 개념이라고 설명할 수가 없어 전체적으로 세세하게 한번 정리해보려고 합니다.

🌱 IoC란?

Inversion of Control

제어의 역전

단순히 제어의 역전 이라는 말로만으로는 잘 이해가 되지 않는 부분이 많습니다.

제어는 어떤 제어를 의미하고, 역전은 그것을 어떻게 역전시킨다는 것인지 차근차근 알아봅시다.

제어란?

제어란 관리하는 것과 동일하다고 생각하면 됩니다.
제가 피자를 먹는과정을 코드로 작성해본다면

public class Me {

    void eat() {
        Pizza pizza = new Pizza();
        pizza.eat();
    }

    public static void main(String[] args) {
        Me me = new Me();
        me.eat();
    }
}

class Pizza {
    public void eat() {
        System.out.println("피자를 먹는다.");
    }
}

위의 코드에서 Me 클래스 자체가 피자 인스턴스를 내부에서 생성함으로써 피자 인스턴스의 생명주기를 관리하는 것을 확인할 수 있습니다.

이와 같이 제어는 객체의 생명주기나 메소드 호출을 직접 관리하는 것 입니다

위 예시의 경우 제어의 흐름이 Me -> Pizza 로 흘러가게 됩니다.

역전이란?

제어의 역전이란 제어의 흐름을 자신이 관리하는 것이 아니라 외부에서 관리하는 것을 의미합니다.

public class Me {

    void eat(Pizza pizza) {
        pizza.eat();
    }

    public static void main(String[] args) {
        Me me = new Me();
        me.eat(new Pizza());
    }
}

class Pizza {
    public void eat() {
        System.out.println("피자를 먹는다.");
    }
}

Pizza 인스턴스를 내부에서 생성하지 않고, 이렇게 외부에서 Me 에게 전달해 주게 되면 제어의 흐름이 Pizza -> Me 로 역전 되게 됩니다. (피자가 결정되고 Me 에게 주입 되어야 Me가 어떤 피자를 먹을지 정해짐)



🌱 IoC가 왜 필요할까?

지금은 치킨을 먹고싶지만 만약 아래와 같은 상황에서 그냥 치킨이 아닌 네네 스노윙 치즈 순살 치킨을 먹고싶어 진다면? 많은 수의 코드 변경이 불가피 할 것입니다.

IOC 적용 이전
public class Me {

    void eat() {
        // Chicken chicken = new Chicken();
        Chicken chicken = new NeneSnowwingCheeseBonelessChicken();
        chicken.eat();
    }

    public static void main(String[] args) {
        Me me = new Me();
        me.eat();
    }
}

class Chicken {
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

그러나 아래와 같이 코드를 작성한다면, 기존에는 그냥 치킨에만 의존적이어서 다른 치킨을 먹으려면 클래스 내부 코드를 고쳐야 했던 제가 외부로부터 음식을 입력 받음으로써 변경에 자유로워 질 수 있습니다.

IOC 적용 이후
public class Me {

    void eat(Chicken chicken) {
        Chicken chicken = anychicken;
        chicken.eat();
    }

    public static void main(String[] args) {
        Me me = new Me();
        me.eat(new NeneSnowwingCheeseBonelessChicken());
    }
}

정리

정리하자면, 기존에는 치킨에 대한 제어권을 제가 가지고 있었지만, 이제는 치킨에 대한 제어권을 외부가 가지게 되는 것입니다.
IoC는 객체지향의 원칙을 잘 지키기 위해 필요하게 되고, 역할과 책임을 분리해 결합도를 낮추고 응집도를 높임으로써 변경을 더 쉽게 할 수 있는 유연한 코드 구조가 될 수 있기 때문에 더욱 필요합니다.

💡 Spring에서 스프링 컨테이너(IoC 컨테이너)가 빈을 주입시켜 주는 것도 IoC를 지키는 행위 입니다.



🌱 DIP

Dependency Inversion Principle

의존 역전 원칙

IoC를 알아가다보면 항상 나오던 DIP에 대해서도 더 정확하게 정리해 보겠습니다.

DIP의 설명을 보면 아래와 같이 설명하고 있는데요.

상위 레벨의 모듈은 절대 하위 레벨의 모듈에 의존하지 않는다. 둘 다 추상화에 의존해야 한다.

간단하게 설명 하자면 이전의 치킨을 먹는 코드에서 치킨을 의존하지 않고 치킨의 추상화인 음식에 의존한다면, 치킨이 아닌 어떤음식이 오더라도 동일하게 먹기만 하면 됩니다.



📝 중간 정리

IoC 제어의 역전 : 구현체를 직접 생성해서 사용하고 있었다면, 외부로부터 구현체를 입력 받는 것.

Ioc 적용 이전

Ioc 적용 이후

❗️ 아직도 재료들 자체는 구체적으로 의존하고 있음.


DIP 의존 역전 원칙 : 구체화된 클래스(구현)에 의존하지 않고, 추상화된 인터페이스(역할)에 의존 하는 것.

DIP 적용 이후


💡 IoC 와 DIP의 목적 : 클래스간 결합을 느슨하게 하여 클래스의 변경에 따른 다른 클래스에 퍼지는 영향력을 최소화하기 위해서. 애플리케이션을 지속가능하고 확장성 있게 만듦.


🌱 DI

IoCDIP 가 유연하고 확장성 있는 어플리케이션을 만들기 위한 원칙 이었다면
DI(Dependency Injection) 는 IoC를 구현하는 방법 입니다.

❓ 의존성이란??
의존성이란 하나의 클래스를 수정할 경우 다른 클래스에 영향을 미치는 것을 의미합니다. 둘이 엮여있다고 생각하면 편합니다.

DI의 3가지 패턴
1. 생성자 주입
2. Setter 주입
3. Interface 주입

의존성 주입 이전


1. 생성자 주입

DI중 생성자 패턴은 위와 같이 생성자를 통해 외부에서 의존성을 주입 받는 방법 입니다.


2. Setter 주입

Setter 주입은 의존성을 입력받는 Setter메소드를 만들어 해당 메소드를 호출하여 의존성을 주입하는 방법 입니다.

❗️ Setter로 하나하나 지정하다보면 의존성을 누락시키는 경우가 생길 수 있습니다.


3. Interface 주입

인터페이스 주입은 Setter 주입과 비슷하게 다른 메소드를 호출하여 의존성을 주입하지만, @Override로 인해 메소드 구현을 강제 할 수 있습니다.



🌱 Spring의 DI

그렇다면 이러한 DI를 스프링은 어떻게 제공해 줄까요?

Spring은 위 코드에서 자동으로 PostRepository 를 주입해 준다는 사실을 알고 있습니다. 이것이 가능한 이유는 스프링은 빈으로 등록된 객체들을 자동으로 생성 후 스프링 컨테이너에서 관리해 주는데요. 이러한 과정에서 해당 빈에 필요한 의존성도 주입해 줍니다.

스프링으로 의존성을 주입 받는 여러가지 방법은 아래 포스트를 참고하면 됩니다.



참고
profile
작은 걸음이라도 꾸준히

0개의 댓글