코드 예시로 이해하는 제어의 역전(IoC)과 의존성 주입(DI)의 개념

KUKJIN LEE • 2주 전 작성
IoC(Inversion of Control)란 무엇인가?
정의
IoC는 제어의 역전이라는 뜻으로, 프로그램의 흐름을 개발자가 직접 제어하지 않고, 외부의 프레임워크나 컨테이너가 제어를 담당하도록 하는 설계 원칙입니다.
왜 중요한가?
밑에 3가지 이유도 중요하지만 핵심은 재사용성이 아닐까 싶습니다.
-
코드의 유연성과 확장성을 높입니다.
-
의존성 관리가 쉬워집니다.
-
테스트하기 쉬운 구조를 만듭니다.
간단한 비유
IoC를 쉽게 이해하기 위해서 DI까지 묶어서 설명드리겠습니다.
조건 (레고는 무조건 흰색이라고 가정)
-
IoC 적용 전: 레고를 조립하는데, 내가 레고까지 만들고 있는 상황.
-
IoC 적용 후: 레고를 가져와 내가 조립만 하면 됨.
- DI 적용: 레고를 조립하고 있으면 옆에서 누군가 레고에 색을 칠함.
간단한 예시
전통적인 방식 (IoC를 적용하지 않은 경우)
class Engine {
void start() {
System.out.println("Engine started.");
}
}
class Car {
private Engine engine;
public Car() {
this.engine = new Engine(); // 직접 객체를 생성
}
void drive() {
engine.start();
System.out.println("Car is driving.");
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.drive();
}
}
-
Car
클래스가Engine
객체를 직접 생성합니다. -
Car
와Engine
이 강하게 결합되어 있어 확장성이 낮습니다.
IoC 적용 후
class Engine {
void start() {
System.out.println("Engine started.");
}
}
class Car {
private Engine engine;
// 외부에서 객체를 주입받음
public Car(Engine engine) {
this.engine = engine;
}
void drive() {
engine.start();
System.out.println("Car is driving.");
}
}
public class Main {
public static void main(String[] args) {
Engine engine = new Engine(); // 객체를 외부에서 생성
Car car = new Car(engine); // 생성자를 통해 주입
car.drive();
}
}
-
Car
클래스는 더 이상Engine
을 직접 생성하지 않습니다. -
Engine
을 외부에서 주입받아 유연성이 증가합니다.
DI(Dependency Injection)란 무엇인가?
정의
DI는 의존성 주입이라는 뜻으로, 객체가 필요로 하는 의존성을 외부에서 제공(주입)하는 디자인 패턴입니다. DI는 IoC의 한 방법입니다.
DI의 장점
-
의존성 관리가 용이: 객체 간의 결합도가 낮아집니다.
-
코드 재사용성 증가: 의존성을 쉽게 교체하거나 확장할 수 있습니다.
-
테스트 편리성: 모의 객체(Mock Object)를 쉽게 주입하여 테스트를 간단히 수행할 수 있습니다.
DI의 주요 방식
-
생성자 주입
-
세터 주입
-
인터페이스 주입
생성자 주입 예시
class Engine {
void start() {
System.out.println("Engine started.");
}
}
class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
void drive() {
engine.start();
System.out.println("Car is driving.");
}
}
public class Main {
public static void main(String[] args) {
Engine engine = new Engine();
Car car = new Car(engine); // 생성자를 통해 Engine 주입
car.drive();
}
}
세터 주입 예시
class Engine {
void start() {
System.out.println("Engine started.");
}
}
class Car {
private Engine engine;
public void setEngine(Engine engine) {
this.engine = engine;
}
void drive() {
engine.start();
System.out.println("Car is driving.");
}
}
public class Main {
public static void main(String[] args) {
Engine engine = new Engine();
Car car = new Car();
car.setEngine(engine); // 세터를 통해 Engine 주입
car.drive();
}
}
IoC와 DI의 관계
-
IoC는 객체의 제어권을 개발자가 아닌 외부로 넘기는 큰 개념입니다.
-
DI는 IoC를 구현하는 한 가지 방법입니다.
IoC |
DI |
---|---|
설계 원칙 |
디자인 패턴 |
객체 생성과 관리의 제어권을 외부로 넘김 |
의존성을 외부에서 주입 |
Spring Framework에서 IoC와 DI
-
IoC 컨테이너: Spring이 제공하는 컨테이너로, 객체의 생성과 생명 주기를 관리합니다.
-
DI 구현:
@Autowired
또는@Bean
등을 사용하여 의존성을 주입합니다.
Spring 예시
생성자 주입
@Component
class Engine {
void start() {
System.out.println("Engine started.");
}
}
@Component
class Car {
private final Engine engine;
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
void drive() {
engine.start();
System.out.println("Car is driving.");
}
}
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
Car car = context.getBean(Car.class);
car.drive();
}
}
요약
-
IoC는 객체 제어권을 외부로 넘기는 설계 원칙입니다.
-
DI는 IoC를 구현하는 방법 중 하나로, 객체 간의 의존성을 외부에서 주입합니다.
-
IoC와 DI는 코드의 유연성과 재사용성을 높이고, 테스트를 용이하게 합니다.
-
Spring Framework는 IoC와 DI를 쉽게 구현할 수 있도록 지원합니다.