본문 바로가기

Flutter

get_it, injectable 패키지를 이용한 의존성의 주입

의존성 주입(Dependency Injection)이란

class Car {
  Engine engine = Engine();
}

class Engine {}

위와 같은 코드는 Car라는 클래스가 Engine 클래스에 의존하고 있다. 그리고 Car 내부에서 직접 Engine 클래스 생성자를 사용하고 있다

따라서 Engine 외 Gas와 같은 다른 클래스를 사용하는 차를 만들고 싶으면 새로운 클래스를 만들어야 한다. 

위 코드를 시각화 한 그림, Car가 Engine에 완전히 의존하고 있다

class Car {
  Engine engine;

  Car(this.engine);
}

class Engine {}

위 코드도 마찬가지로 Car 내부에서 Engine을 사용하지만 Engine을 외부에서 생성해서 주입하고 있다.

따라서 어떠한 Engine이 들어와도 Car 클래스를 재사용 할 수 있는 가능성이 높아진다.

마찬가지로 위 코드를 시각화 한 것, Car와 Engine이 분리되어 있음

의존성을 주입하는 방식에는 여러가지가 있지만, 가장 권장되는 방법은 생성자에 주입하는 방식이다. 따라서 정리하자면, 일반적인 의존성 주입이라고 하는 것은 외부에서 생성한 인스턴스를 생성자를 통해 주입하는 것이다.

get_It , Injectable 패키지를 통한 의존성 주입

https://pub.dev/packages/get_it

 

get_it | Dart Package

Simple direct Service Locator that allows to decouple the interface from a concrete implementation and to access the concrete implementation from everywhere in your App"

pub.dev

https://pub.dev/packages/injectable

 

injectable | Dart Package

Injectable is a convenient code generator for get_it. Inspired by Angular DI, Guice DI and inject.dart.

pub.dev

get_it 패키지는 중앙에서 인스턴스를 가지고 있다가 필요한 곳에 제공하는 service locator의 역할을 하며, 싱글톤과 팩토리 패턴 두 형태 모두 사용할 수 있다.

injectable은 get_it을 통한 의존성 주입을 더 편리하게 할 수 있도록 도와주는 패키지이다.

Installation

dependencies:  
  # add injectable to your dependencies  
  injectable:  
  # add get_it  
  get_it:  
  
dev_dependencies:  
  # add the generator to your dev_dependencies  
  injectable_generator:  
  # add build runner if not already added  
  build_runner:

Setup

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

final getIt = GetIt.instance;

@InjectableInit()
void configureDependencies() => getIt.init();

 

파일을 생성한 후, di와 관련된 전반적인 코드를 작성하며 이 곳에서는 초기 설정에 대한 여러가지 옵션을 줄 수 있다. GetIt의 인스턴스는 생성해두고 가져다가 사용한다.

이 후 build_runner를 실행 시켜주면 .config.dart 파일이 생성된 것을 확인할 수 있다.

선언한 configureDependencies 함수는 main() 안에서 호출해준다.

void main() {  
 configureDependencies();  
 runApp(MyApp());  
}

Registering factories

@injectable  
class ServiceA {}

@injectable 어노테이션을 사용하면 해당 클래스를 사용할 때 마다 인스턴스를 생성할 수 있다. get_it의 registerFactory() 메서드와 같은 역할을 수행하게 된다.

Registering singletons

@singleton // or @lazySingleton  
class ApiProvider {}

@singleton 또는 @lazySingleton 어노테이션을 사용하면 해당 클래스를 싱글톤으로 생성할 수 있다. get_it의 registerSingleton(), registerLazySingleton()과 같은 역할을 수행한다.

Disposing of singletons 

@singleton // or lazySingleton  
class DataSource {  
  
  @disposeMethod  
  void dispose(){  
    // logic to dispose instance  
  }  
}

@disposeMethod 어노테이션을 통해 싱글톤을 폐기해야하는 상황에서의 로직을 처리할 수 있다.

@Singleton(dispose: disposeDataSource)  
class DataSource {  
  
  void dispose() {  
    // logic to dispose instance  
  }  
}  
/// dispose function signature must match Function(T instance)  
FutureOr disposeDataSource(DataSource instance){  
   instance.dispose();  
}

@Singleton 또는 @LazySingleton의 속성으로 dispose가 있는 것을 확인할 수 있다. 해당 속성의 인자로 dispose시 호출될 메서드를 전달하면 첫번째 예시와 마찬가지로 싱글톤 폐기 시 로직을 처리할 수 있다.

FactoryMethod and PostConstruct Annotations

@injectable  
class MyRepository {  
  @factoryMethod  
  MyRepository.from(Service s);  
}

@factoryMethod 어노테이션은 말 그대로 dart lang의 factory 메서드를 지원하는 어노테이션이다. static 메서드, factory 키워드, named constructor에서 모두 사용 가능하다.

@Injectable()  
class SomeController  {  
  SomeController(Service service);  
  
  @PostConstruct()  
  void init() {  
   //...init code  
   }  
  
}

@PostConstruct는 의존성 생성 시 초기화 하는 코드를 작성할 수 있게 해주는 어노테이션이다. 여기에는 public한 요소만 포함될 수 있다.

Registering third party types 

@module  
abstract class RegisterModule {  
  @singleton  
  ThirdPartyType get thirdPartyType;  
  
  @prod  
  @Injectable(as: ThirdPartyAbstract)  
  ThirdPartyImpl get thirdPartyType;  
}

Database와 같은 서드파티 객체를 주입하려면 module 클래스를 생성하고 내부에 추가하면 된다.

Pre-Resolving futures 

@module  
abstract class RegisterModule {  
  @preResolve  
  Future<SharedPreferences> get prefs => SharedPreferences.getInstance();  
}

Future를 기다리고, 생성된 값을 의존성 객체로 등록하고 싶다면 @preResolve 어노테이션을 사용해야한다. 만약 이를 사용할 경우에는 위에서 다루었던 init 메서드 또한 Future로 변경해주어야한다.

Passing Parameters to factories 

@injectable  
class BackendService {  
  BackendService(@factoryParam String url);  
}

인스턴스 생성 시에 파라미터를 전달하고 싶다면 해당 파라미터 앞에 @factoryParam 어노테이션을 붙여주면 된다. 파라미터는 최대 2개까지 등록 가능하다. 이유는 get_it 패키지에서 2개로 제한을 하고 있기 때문이다.

 

마지막으로 위와 같은 방법으로 등록된 인스턴스들을 사용하려면 글로벌로 등록된 getIt을 통해 getIt<T>()와 같은 형태로 가져다가 사용할 수 있다.