MVVM Pattern in Flutter 3 - Dependency Injection

mmmYoung·2023년 4월 10일
2

플러터

목록 보기
8/11

Dependency Injection - 의존성 주입


실제 위와 같이 쓰면 이렇게 쓰는 거 아닌 것 같은데.. 라는 느낌이 들 것이다.

MVVM을 사용하는 이유는 코드의 가독성과 쉬운 유지보수, testable한 각각의 레이어를 만들기 위함이다.
저렇게 되면 뷰모델을 테스트하기 위해, 유즈케이스, 레포지토리, 데이터 소스까지 모두 끼워넣게 된다. (각 레이어가 다른 레이어에 의존하게 됨)
그렇게 되면 테스트가 실패해도.. 어디가 잘못된 지 모를 것임..
그래서 DI를 알아야한다.
DI는 의존성 관리를 외부에서 처리하고, 필요한 곳에서 이를 주입받아 사용하는 방식이다.

GetIt Package

그래서 이용하는 대표적인 DI 패키지는 GetIt이다.

사용법

간단히 말하면 하나의 파일에 모든 객체들을 등록하고, 다른 파일에서는 해당 파일을 import해서 사용하면 된다.

일단, 의존성을 관리할 di.dart파일을 만들어주자.

final locator = GetIt.instance;

void setupLocator() {
  locator.registerLazySingleton(() => FirebaseImageDataSource());
  locator.registerLazySingleton<ImageRepository>(() => ImageRepositoryImpl(locator<FirebaseImageDataSource>()));
  locator.registerLazySingleton(() => CreateImageUseCase(locator<ImageRepository>()));
  
  ...
  
  locator.registerFactory(() => ImageViewModel(
      locator<CreateImageUseCase>(),
      locator<LoadImageUseCase>(),
      locator<DeleteImageUseCase>(),
  ));
}

메인함수에서 runApp이 실행되기 전에 setupLocator 메서드를 실행시켜주어야한다.
등록은 이게 끝이다,,

사용하려면 다음처럼 하면 된다.

class ImageViewModel {
  final CreateImageUseCase _createImageUseCase;
  final LoadImageUseCase _loadImageUseCase;
  final DeleteImageUseCase _deleteImageUseCase;

  ImageViewModel()
      : _createImageUseCase = GetIt.I.get<CreateImageUseCase>(),
        _loadImageUseCase = GetIt.I.get<LoadImageUseCase>(),
        _deleteImageUseCase = GetIt.I.get<DeleteImageUseCase>();

  Future<void> createImage(String filePath) async {
    try {
      await _createImageUseCase(filePath);
    } catch (e) {
      // handle error
    }
  }
  
  ...
  
}

GetIt.I.get()이면 유즈케이스를 참조하기 위해 레포지토리, 데이터소스를 파고 파고 들어가지 않아도 된다. (GetIt.instance = GetIt.I이다)

코드를 더 자세히 살펴보자.

registration

GetIt에서, register하는 방법은 여러가지가 있다.

getIt.registerFactory

의존성 주입할 때마다 새로운 인스턴스를 생성하고, 사용한다. 즉, 호출할 때마다 인스턴스가 새로 만들어집니다.
위 코드에서도 매 번 새로운 인스턴스가 필요한 뷰모델에만 registerFactory를 사용하였다.

getIt.registerSingleton

해당 타입의 인스턴스를 최초 한 번 생성하고, 그 이후에 호출될 때마다 동일한 인스턴스를 사용한다. db 연결 객체나 SharedPreferences 등 상태를 가지는 객체에 적합하다.
즉 애플리케이션 전반에 걸쳐 공유해야 할 인스턴스를 등록할 때 사용된다.
이 방법은 앱 시작 시 시간이 많이 소요될 수 있으므로 아래의 레이지 싱글톤을 사용하여 인스턴스 생성 시간을 미룰 수 있습니다.

getIt.registerLazySingleton

registerSingleton과 비슷하지만, 인스턴스 생성을 앱 시작이 아니라 처음 사용될 때 하게 된다. 이 방법은 registerSingleton과 달리 애플리케이션 시작 시간을 최적화할 수 있.

imageRepository 등록 방법이 조금 다른데?

locator.registerLazySingleton<ImageRepository>(() => 
  ImageRepositoryImpl(locator<FirebaseImageDataSource>())); 

이 코드는 ImageRepository 추상 클래스를 구현한 클래스 중 ImageRepositoryImpl 클래스의 생성하는 코드이다.
인스턴스를 GetIt으로부터 한 번만 생성하고, 그 이후로는 이미 생성된 인스턴스를 계속해서 반환합니다. ImageRepositoryImpl 클래스의 생성자는 FirebaseImageDataSource 클래스의 구현체를 인자로 받는데,
참고로 등록은 LazySingleton 방식을 사용해서 한 번 생성한 인스턴스를 계속해서 사용할 수 있기 때문에, 메모리를 절약하면서도 효율적인 의존성 주입이 가능해진다.

의존성 분리랑 의존성 주입이랑 무슨 차이인가여?

의존성 분리는 소프트웨어 개발에서 의존하는 구성요소간의 결합도를 낮추기 위해 사용하는 설계 원칙입니다. 즉, 한 구성요소가 다른 구성요소에 의존하지만, 구성요소 간의 결합도가 낮아져 유지보수성이 높아지는 것을 목표로 합니다.

반면에 의존성 주입은 의존하는 객체를 직접 생성하는 것이 아니라 외부에서 생성한 객체를 주입해주는 것을 말합니다. 이를 통해 객체 간의 결합도를 낮출 수 있으며, 유연한 코드를 작성할 수 있게 됩니다.

즉, 의존성 분리는 설계 원칙이며, 의존성 주입은 그 원칙을 구현하는 방법 중 하나입니다. 의존성 주입을 통해 의존하는 객체 간의 결합도를 낮출 수 있으며, 유지보수성이 높은 코드를 작성할 수 있습니다.

결론

테스트 코드를 직접 만들어 보면서 이것이 왜 필요한지 또 또 알아봅시다!

참고 사이트
https://ctoahn.tistory.com/15
https://rodrigolmti.medium.com/flutter-di-a-true-love-story-1e5a5ae2ba2d
https://betterprogramming.pub/how-to-use-mvvm-in-flutter-4b28b63da2ca

profile
안냐세여

0개의 댓글