Flutter에서의 의존성 주입(Dependency Injection) 이해하기

Baek Dong Hyun·2025년 2월 23일
1

1. 의존성 주입이 뭘까?

의존성 주입(Dependency Injection, DI)은 객체 간의 의존성을 외부에서 주입하는 설계 패턴입니다.

쉽게 말해, 클래스가 직접 다른 객체를 생성하는 것이 아니라, 외부에서 제공된 객체를 사용하는 것을 의미합니다. 이를 통해 코드의 유지보수성을 높이고, 결합도를 낮출 수 있습니다.

📌 예를 들어보자!

우리가 식당에서 음식을 먹을 때, 주방장이 모든 재료를 직접 구하고, 요리하고, 서빙까지 한다면 엄청 비효율적입니다.
하지만 주방장은 재료 공급업체(외부)에서 재료를 받아서 요리를 만들기만 하면 됩니다.
이와 마찬가지로, 코드에서도 클래스가 직접 필요한 객체를 만들지 않고, 외부에서 주입받으면 더 효율적으로 관리할 수 있습니다.


2. 그렇다면 의존성 주입을 왜 사용해야 하는 걸까?

의존성 주입을 사용하면 다음과 같은 장점이 있습니다.

  • 유지보수성 향상: 객체 간의 결합도를 낮추어 코드 수정이 용이해집니다.
  • 테스트 용이성: Mock 객체를 활용하여 단위 테스트를 쉽게 수행할 수 있습니다.
  • 코드 재사용성 증가: 특정 기능을 여러 곳에서 쉽게 재사용할 수 있습니다.

3. 만약 의존성 주입을 사용하지 않게 되면 앱에 어떠한 영향을 끼칠까?

의존성 주입 없이 코드를 작성하면 다음과 같은 문제가 발생할 수 있습니다.

의존성 주입 없이 작성한 코드 예제

class ApiService {
  void fetchData() {
    print("Fetching data from API...");
  }
}

class HomeScreen {
  final ApiService apiService = ApiService(); // 직접 객체 생성
}

📌 문제점

  • HomeScreenApiService를 직접 생성하므로 두 클래스 간의 강한 결합(Tightly Coupled) 이 발생합니다.
  • ApiService를 변경하면 HomeScreen도 함께 수정해야 하는 상황이 발생할 수 있습니다.
  • 객체의 테스트가 어려워지고, 코드의 유지보수성이 떨어집니다.

🔍 강한 결합(Tightly Coupled)이란?

강한 결합이란, 한 클래스가 다른 클래스에 강하게 의존하고 있는 상태를 말합니다. 즉, 한 클래스가 변경되면, 이를 사용하는 다른 클래스도 변경해야 하는 상황을 의미합니다.

💡 그럼 강한 결합이 있으면 약한 결합도 있을까?

  • 강한 결합(Tightly Coupled): 클래스 간의 의존성이 높아 변경이 어렵다.
  • 약한 결합(Loosely Coupled): 클래스 간의 의존성이 낮아 변경이 용이하다.

의존성 주입을 활용하면 강한 결합을 약한 결합으로 바꿀 수 있어 코드의 유지보수성과 확장성이 높아집니다.


4. 의존성 주입을 하는 방법

의존성 주입을 구현하는 다양한 방법이 있습니다.

1) 생성자를 통한 주입 (Constructor Injection)

class ApiService {
  void fetchData() {
    print("Fetching data from API...");
  }
}

class HomeScreen {
  final ApiService apiService;

  HomeScreen(this.apiService); // 외부에서 객체를 주입
}

이제 HomeScreenApiService를 직접 생성하는 것이 아니라, 외부에서 주입받도록 변경되었습니다. 이를 통해 클래스 간의 결합도를 낮추고, 코드의 유연성과 테스트 용이성이 높아집니다.

2) Provider를 활용한 주입

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class ApiService {
  void fetchData() {
    print("Fetching data from API...");
  }
}

void main() {
  runApp(
    MultiProvider(
      providers: [
        Provider(create: (_) => ApiService()), // ApiService 주입
      ],
      child: MyApp(),
    ),
  );
}

3) GetIt을 활용한 Service Locator 패턴

import 'package:get_it/get_it.dart';

final getIt = GetIt.instance;

void setupLocator() {
  getIt.registerLazySingleton<ApiService>(() => ApiService());
}

4) Injectable을 활용한 자동 의존성 주입

import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';

final getIt = GetIt.instance;

()
void configureDependencies() => $initGetIt(getIt);

5) Riverpod을 활용한 의존성 주입

import 'package:flutter_riverpod/flutter_riverpod.dart';

final apiServiceProvider = Provider((ref) => ApiService());

5. 그렇다면 의존성 주입에 관련된 패키지는 어떠한게 있을까?

  • Provider: Flutter에서 권장하는 상태 관리 및 의존성 주입 패턴
  • GetIt: Service Locator 패턴을 활용한 전역적 객체 관리
  • Injectable: GetIt 기반으로 DI 자동화를 지원하는 패키지
  • Riverpod: Provider의 개선 버전으로 더 안전하고 강력한 기능 제공

6. 의존성 주입을 하기 편한 상태관리 패키지는 어떠한게 있을까?

  • Provider: 기본적인 상태 관리 및 DI를 함께 지원
  • Riverpod: Provider보다 더 강력한 기능을 제공
  • GetX: 간단하고 직관적인 상태 관리 및 DI 지원
  • Bloc: 대규모 프로젝트에 적합한 상태 관리 방식

7. 이런 것들을 응용해서 어떠한 이점이 있을까?

  • 코드 유지보수성이 향상됨
  • 테스트가 쉬워짐 (Mock 객체 사용 가능)
  • UI와 비즈니스 로직을 분리하여 가독성 증가
  • 재사용 가능한 코드 작성 가능

8. Provider, Riverpod, GetIt, Injectable 간단 비교

방식장점단점
Provider공식 권장, 간단하고 직관적규모가 커질수록 관리 어려움
RiverpodProvider 개선판, 안전하고 간결함러닝 커브가 있음
GetIt전역적 객체 관리 가능, 사용이 간편Service Locator 패턴에 대한 이해 필요
InjectableGetIt과 함께 사용 가능, DI 자동화설정이 필요함

9. 마무리

Flutter에서 의존성 주입(Dependency Injection)은 코드의 유지보수성을 높이고, 확장성을 증가시키는 중요한 개념입니다.

코드의 결합도를 줄여 유지보수를 쉽게 하고,
테스트가 용이해지며,
재사용 가능한 구조를 만들 수 있습니다.

의존성 주입을 구현하는 방법에는 Provider, GetIt, Riverpod, Injectable과 같은 다양한 패키지가 있으며, 프로젝트의 규모와 필요에 따라 적절한 방법을 선택하는 것이 중요합니다.

profile
안녕하세요.

0개의 댓글

관련 채용 정보