프로그래밍/CS

IoC, DI (without Spring)

hwangsehee 2025. 4. 1. 21:29

 

IoC , DI 의 필요성


SOLID 원칙에서 DIP 원칙(의존관계 역전 원칙)을 살펴보자

추상화에 의존해야지, 구현체에 의존하면 안된다. (역할과 구현이 분리가 되어야함)

class Service {
    void doSomething() {
        System.out.println("Service 동작");
    }
}

class Client {
    private Service service = new Service(); // 직접 객체 생성

    void execute() {
        service.doSomething();
    }
}

❎ Client가 Service를 의존한다. → DIP 원칙 위배

(Service 변경 시 Client 클래스도 변경되어야하기 때문)


DIP를 위해서 IoC를 사용하면 편리하고, IoC를 구현하는 방법 중 하나가 DI이다.

  • Service 클래스 → 인터페이스 변경 , Service 인터페이스를 구현하는 구현체 DefaultService 생성
// 1. 인터페이스 정의
interface Service {
    void doSomething();
}

// 2. 인터페이스 구현체 (기본 서비스)
class DefaultService implements Service {
    public void doSomething() {
        System.out.println("Default Service 동작");
    }
}

// 3. 클라이언트 클래스 - 인터페이스에 의존
class Client {
    private Service service;

    // 의존성 주입 (Dependency Injection)
    public Client(Service service) {
        this.service = service;
    }

    void execute() {
        service.doSomething();
    }
}

//4. 외부에서 객체 주입 
public static void main(String[] args) {
    Client client = new Client(new DefaultService()); //객체 주입
    client.doSomething(); // "Default Service 동작" 출력
}

☑️ Client 클래스는 더이상 구현체를 의존 하지않고 추상체를 의존한다.

☑️ Client는 구현체가 뭔지 몰라도 된다. 인터페이스만 알면 됨!

☑️ 이는 외부에서 구현체를 결정할 수 있게 해준다. (IoC 적용!)

IoC , DI 정의


IoC : Inversion of Control 제어의 역전

코드의 흐름을 제어하는 주체가 바뀌는 것.

  • 제어권이 뒤바뀐다. (개발자 → 프레임워크)
  • 스프링만의 고유 특징이 아니고 프레임워크의 특징
  • 프로그램 흐름을 직접 제어하는것이 아니라 외부에서 관리하는 것을 제어의 역전이라고 한다.
// 전략 인터페이스
interface Strategy {
    void execute();
}

// 전략 A
class StrategyA implements Strategy {
    public void execute() {
        System.out.println("전략 A 실행");
    }
}

// 전략 B
class StrategyB implements Strategy {
    public void execute() {
        System.out.println("전략 B 실행");
    }
}

// 컨텍스트 (제어의 역전 담당)
class Context {
    private final Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public void run() {
        strategy.execute(); // 실행 흐름을 컨텍스트가 제어 (IoC 적용)
    }
}

// 클라이언트 코드
public class Main {
    public static void main(String[] args) {
        Context context = new Context(new StrategyA()); // 실행 전략을 외부에서 주입
        context.run();
    }
}

☑️  클라이언트가 직접 실행할 전략을 결정하지않음

☑️  실행흐름은 Context가 관리 → IoC 적용

DI : Dependency Injection 의존성 주입

필요로하는 객체를 스스로 생성하는 것이 아닌 외부로 부터 주입받는 설계 패턴

  • 의존관계 주입(DI) 를 사용하면 정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다.
  • DI는 IoC 를 구현하는 한가지 방법이다.
  • 코드의 결합도를 낮추고, 각 컴포넌트의 독립성을 증가시킨다.

생성자 주입

//생성자 주입 
public class Controller{
	private final Service service;
	
	public Controller(Service service){
		this.service = service;
	}
}
  • 생성자의 호출 시점에 1회 호출되는 것이 보장된다.
  • 주입받은 객체가 변하지않거나(불변성), 반드시 객체의 주입이 필요한 경우에 강제하기 위해 사용 가능 → OCP : 확장에는 열려있으나 변경에는 닫혀있어야 한다.
  • test코드 작성시 프레임워크 없이 순수 java코드로 작성 가능 (Spring test는 컴포넌트들을 등록하고 초기화 하는 비용 소요)
  • 주입하는 객체가 누락된 경우 컴파일 시점에서 오류 발견 가능
  • 위 같은 이유로 Spring에서는 생성자 주입을 권장

Setter 주입

//setter 주입
public class Controller{
	private Service service;
	
	public void SetSetvice(Service service){
		this.service= service;
	}
}
  • 생성자 주입과 다르게 주입받는 객체가 변경될 가능성이 있는 경우에 사용한다.

필드 주입

//필드 주입 
@Autowired 
private Service service;
  • 필드 주입을 이용하면 코드가 간결해져서 과거에 많이 이용되었었다.
  • 하지만 필드 주입은 외부에서 접근이 불가능하고, 테스트 코드의 중요성이 부각됨에 따라 필드의 객체를 수정할 수 없는 필드 주입은 거의 사용되지 않았다.
  • 또한 필드 주입은 반드시 DI 프레임워크가 존재해야 하므로 반드시 사용을 지양해야 한다.(Autowired)

참고 블로그

다양한 의존성 주입방법, 생성자 주입을 사용해야 하는 이유 : https://mangkyu.tistory.com/125

 

 

+)

데브코스에서 매주 금요일에 진행하는 RBF!

이번엔 나름 준비를 열심히해서 혼자 보기 아까운 마음에 포스팅해봅니다 총총.. 💨