IoC(Inversion of Control) - 제어의 역전
- IoC는 프로그램의 제어 흐름을 사용자가 아닌 프레임워크가 관리한다는 것을 의미한다. IoC를 사용하면 프로그램의 흐름을 프레임워크에 위임하여, 개발자는 비즈니스 로직에 집중하며, 코드의 재사용성과 유지보수성이 향상된다.
제어 흐름을 관리한다는 것은 프로그램이나 애플리케이션에서 코드의 실행 순서나 방식을 결정하고 조절하는 과정을 의미한다. 예를 들어, 함수나 메서드 호출, 반복문 실행, 조건문 평가 등을 통해 어떤 코드가 언제 실행될지를 결정할 때, 개발자가 결정하는 것이 아닌 프레임워크나 라이브러리 같은 외부 시스템에 위임한다. 이 방식에서 프레임워크가 애플리케이션의 주요 실행 흐름을 책임지고, 개발자는 프레임워크가 요구하는 특정 부분에만 코드를 제공함으로써 개발자는 비즈니스 로직 및 애플리케이션 핵심 기능에 더 집중할 수 있다.
예시)
- 이벤트 기반 프로그래밍: 사용자 인터페이스 구성 요소에서 발생하는 버튼 클릭, 텍스트 입력 등의 이벤트에 반응하여 실행되는 코드 조각을 정의할 때, 프로그램의 주요 제어 흐름은 사용자의 상호작용에 의해 결정되며, 개발자는 이벤트에 대한 반응만을 정의한다.
- 콜백 함수: 비동기 작업이 완료될 때 실행될 함수를 전달하는 방식이다. 예를 들어, 네트워크 요청이 완료된 후 결과를 처리하는 코드를 콜백 함수로 정의할 때, 주요 제어 흐름은 네트워크 요청의 처리 과정에 의해 결정되며, 개발자는 결과 처리 방식만을 정의한다.
- 프레임워크와 라이브러리: 스프링, 장고 등의 프레임워크는 라우팅, 요청 처리, 응답 전송 등의 주요 제어 흐름을 관리하고, 개발자는 각 요청에 대한 처리 로직(컨트롤러, 뷰, 모델 등)만을 구현한다.
DI(Dependency Injection) - 의존성 주입
- DI는 객체가 직접 의존성(다른 객체나 리소스)을 생성하는 대신, 이러한 의존성을 외부에서 주입받는 방식을 말한다. 이를 통해 클래스 간의 결합도가 낮아지고, 유닛 테스트가 용이해지며, 코드의 유지보수성과 확장성이 향상된다.
- Constructor Injection (생성자 주입): 의존성을 객체 생성 시 생성자를 통해 주입합니다.
- Setter Injection (세터 주입): 의존성을 설정하는 세터 메서드를 통해 주입합니다.
예시) Car 클래스가 Engine 클래스의 기능을 필요로 할 때, Car 클래스 내부에서 Engine 클래스의 인스턴스를 생성하면, Car는 Engine에 의존하게 된다. 이 때 Car 클래스 내에서 직접 new Engine()과 같은 방식으로 Engine 클래스의 인스턴스를 생성한다.
public class Car {
private Engine engine;
public Car() {
engine = new Engine(); // 의존성 생성
}
}
이러한 방식은 Car 클래스가 Engine 클래스의 구체적인 구현에 강하게 결합되어 있음을 의미하며, 이는 유연성과 재사용성의 저하, 테스트의 어려움과 같은 문제를 야기할 수 있다. Car 클래스는 Engine의 특정 구현에 의존하게 되며, Engine 클래스를 변경하거나 다른 종류의 엔진으로 교체하려면 Car 클래스의 코드도 함께 변경해야한다. 이러한 문제를 해결하기 위한 방법으로 위에 제시된 세가지 방법으로 의존성을 외부에서 주입한다.
생성자 주입(Constructor Injection): 객체를 생성할 때 생성자를 통해 의존성을 전달한다.
public class Car {
private Engine engine;
public Car(Engine engine) { // 생성자를 통한 의존성 주입
this.engine = engine;
}
}
세터 주입(Setter Injection): 객체 생성 후, 세터 메서드를 통해 의존성을 주입한다.
public class Car {
private Engine engine;
public void setEngine(Engine engine) { // 세터 메서드를 통한 의존성 주입
this.engine = engine;
}
}
의존성 주입 순서
1. 먼저, 의존성을 주입할 객체의 기능을 정의하는 인터페이스를 생성한다.
public interface Engine {
void start();
}
2. 인터페이스를 구현하는 하나 이상의 구체적인 클래스를 정의한다.
public class GasolineEngine implements Engine {
@Override
public void start() {
System.out.println("가솔린 엔진이 시작됩니다.");
}
}
public class ElectricEngine implements Engine {
@Override
public void start() {
System.out.println("전기 엔진이 시작됩니다.");
}
}
3. 의존성을 주입받을 클래스에서 인터페이스 타입을 사용하여 의존성을 선언하고, 생성자, 세터 메서드, 또는 다른 방식을 통해 외부에서 인터페이스 구현 객체를 주입받는다.
public class Car {
private Engine engine; // 인터페이스 타입의 필드
public Car(Engine engine) { // 생성자를 통한 의존성 주입
this.engine = engine;
}
public void startCar() {
engine.start(); // 인터페이스의 메서드 호출
}
}
4. 클라이언트 코드에서 구체적인 Engine 구현을 생성하고, Car 객체에 주입한 다음 사용한다.
public class Main {
public static void main(String[] args) {
Engine gasolineEngine = new GasolineEngine(); // 구체적인 구현 생성
Car car = new Car(gasolineEngine); // 의존성 주입
car.startCar(); // 사용
}
}
'공부 > Spring' 카테고리의 다른 글
동적 쿼리란? - Querydsl (0) | 2025.02.10 |
---|---|
Spring 데이터 접근 기술 (1) | 2025.02.10 |
WebClient 사용을 위한 배경지식 및 개념 (0) | 2024.03.01 |
객체 지향 프로그래밍 - 다형성 (0) | 2024.02.25 |
객체 지향 프로그래밍 - SOLID (0) | 2024.02.25 |