의존성 주입(Dependency Injection)이란
class Car {
Engine engine = Engine();
}
class Engine {}
위와 같은 코드는 Car라는 클래스가 Engine 클래스에 의존하고 있다. 그리고 Car 내부에서 직접 Engine 클래스 생성자를 사용하고 있다
따라서 Engine 외 Gas와 같은 다른 클래스를 사용하는 차를 만들고 싶으면 새로운 클래스를 만들어야 한다.
class Car {
Engine engine;
Car(this.engine);
}
class Engine {}
위 코드도 마찬가지로 Car 내부에서 Engine을 사용하지만 Engine을 외부에서 생성해서 주입하고 있다.
따라서 어떠한 Engine이 들어와도 Car 클래스를 재사용 할 수 있는 가능성이 높아진다.
의존성을 주입하는 방식에는 여러가지가 있지만, 가장 권장되는 방법은 생성자에 주입하는 방식이다. 따라서 정리하자면, 일반적인 의존성 주입이라고 하는 것은 외부에서 생성한 인스턴스를 생성자를 통해 주입하는 것이다.
get_It , Injectable 패키지를 통한 의존성 주입
https://pub.dev/packages/get_it
https://pub.dev/packages/injectable
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>()와 같은 형태로 가져다가 사용할 수 있다.
'Flutter' 카테고리의 다른 글
Flutter Version Management(FVM) 사용법 정리(Mac) (0) | 2024.05.23 |
---|---|
flutter_gen 패키지를 이용하여 asset 사용하기 (0) | 2023.11.27 |
Flutter MVVM Architecture (0) | 2023.05.30 |
Flutter Mockito를 이용한 API 통신 Unit Test (0) | 2023.04.19 |
Flutter InheritedWidget (0) | 2023.04.18 |