학습
Dart 의존성 주입
Dart 의존성 주입 — 고급
의존성 주입
1. 도입 (왜 배워야 하는가)
의존성 주입(Dependency Injection, DI)은 소프트웨어 개발에서 객체 간의 의존성을 관리하는 패턴입니다. 이 패턴을 이해하고 활용하는 것은 특히 대규모 애플리케이션을 개발할 때 필수적입니다. DI를 사용하면 코드의 재사용성, 테스트 용이성, 유지보수성을 높일 수 있습니다.
만약 DI를 모르고 개발하게 되면, 객체들이 서로 강하게 결합되어 유지보수가 어렵고, 테스트가 복잡해지며, 코드가 지저분해질 수 있습니다. 예를 들어, 특정 클래스가 다른 클래스의 인스턴스를 직접 생성한다면, 그 클래스는 해당 클래스에 강한 의존성을 가지게 되어, 나중에 변경이 필요할 때 모든 관련 코드를 수정해야 하는 상황이 발생할 수 있습니다.
2. 핵심 개념 설명
의존성 주입의 주요 개념은 다음과 같습니다:
1. 의존성(Dependency)
어떤 객체가 다른 객체를 필요로 하는 관계를 의미합니다. 예를 들어, 자동차가 엔진 없이 존재할 수 없는 것처럼, 특정 클래스가 다른 클래스를 필요로 하는 경우입니다.
2. 주입(Injection)
의존성을 외부에서 주입하는 과정을 의미합니다. 이는 객체의 생성자가 아닌 외부에서 의존성을 주입하여 객체 간의 결합도를 낮춥니다.
3. IoC (Inversion of Control)
제어의 역전 원칙으로, 객체가 직접 다른 객체의 의존성을 생성하지 않고, 대신 외부에서 주입받는 패턴입니다.
| 용어 | 정의 |
|---|---|
| 의존성 | 객체가 다른 객체를 필요로 하는 관계 |
| 주입 | 의존성을 외부에서 객체에 주입하는 과정 |
| IoC | 제어의 역전, 객체 간의 결합도를 낮추는 원칙 |
3. 기본 코드 예제
가장 기본적인 의존성 주입을 보여주는 코드입니다.
class Engine {
void start() {
print("엔진이 시작되었습니다.");
}
}
class Car {
final Engine engine;
Car(this.engine); // 의존성을 생성자에서 주입받음
void start() {
engine.start(); // 주입된 엔진을 사용
}
}
void main() {
Engine myEngine = Engine(); // 엔진 객체 생성
Car myCar = Car(myEngine); // 엔진을 주입하여 자동차 객체 생성
myCar.start(); // 자동차 시작
}
실행 결과:
엔진이 시작되었습니다.
코드 설명:
Engine클래스 정의: 자동차의 엔진을 나타냅니다.Car클래스 정의:Engine객체를 생성자의 매개변수로 받아 의존성을 주입받습니다.start메소드에서 주입된Engine객체의start메소드를 호출합니다.main()함수에서Engine과Car객체를 생성하고,myCar.start()를 호출하여 엔진을 시작합니다.- 직접 생성하면 테스트가 힘들고, 의존성이 강하게 결합됨.
- 주입된 객체의 상태를 변경하면 불필요한 사이드 이펙트가 발생할 수 있음.
- 의존성 주입은 코드의 재사용성과 테스트 용이성을 높인다.
- 객체 간의 결합도를 낮추어 유지보수가 용이하다.
- IoC 원칙을 통해 객체의 의존성을 외부에서 관리할 수 있다.
- 서비스 로케이터 패턴
- 싱글턴 패턴
- Flutter 상태 관리 패턴 (Provider, Riverpod 등)
4. 실전 활용 예제
예제 1: 쇼핑몰에서 장바구니를 구현할 때
쇼핑몰 애플리케이션에서 장바구니 기능을 구현할 때, 장바구니 클래스가 상품 클래스에 의존성을 가질 수 있습니다.
class Product {
final String name;
Product(this.name);
}
class Cart {
final List<Product> products = [];
void addProduct(Product product) {
products.add(product);
print('${product.name}가 장바구니에 추가되었습니다.');
}
}
void main() {
var cart = Cart();
var apple = Product("사과");
cart.addProduct(apple);
}
실행 결과:
사과가 장바구니에 추가되었습니다.
예제 2: API 클라이언트와 서비스 클래스
API 호출을 위한 클라이언트 클래스와 비즈니스 로직을 처리하는 서비스 클래스를 구현할 수 있습니다.
class ApiClient {
void fetchData() {
print("데이터를 가져왔습니다.");
}
}
class DataService {
final ApiClient apiClient;
DataService(this.apiClient);
void loadData() {
apiClient.fetchData();
}
}
void main() {
var apiClient = ApiClient();
var dataService = DataService(apiClient);
dataService.loadData();
}
실행 결과:
데이터를 가져왔습니다.
5. 흔한 실수와 주의사항
실수 1: 의존성을 직접 생성
잘못된 코드:
class Car {
Engine engine = Engine(); // 의존성을 직접 생성
}
올바른 코드:
class Car {
final Engine engine;
Car(this.engine); // 의존성을 주입
}
실수 2: 주입된 객체의 상태 변경
잘못된 코드:
class Engine {
bool isRunning = false;
}
class Car {
final Engine engine;
Car(this.engine);
void start() {
engine.isRunning = true; // 상태를 변경
}
}
올바른 코드:
class Engine {
void start() {
print("엔진이 시작되었습니다.");
}
}
class Car {
final Engine engine;
Car(this.engine);
void start() {
engine.start(); // 상태 변경 없이 메소드 호출
}
}
6. 정리
다음에 학습하면 좋은 주제: