Factory Function은 Factory Pattern (디자인 패턴 분류 GoF 23가지 중 하나) 을 구현한 방법 중 하나다.
() => ViewModel(useCase: getIt())// UseCase: 비즈니스 로직을 수행하는 클래스
class GetUserDataUseCase {
final UserRepository repository;
GetUserDataUseCase({required this.repository});
Future<User> execute(String userId) async {
return await repository.getUser(userId);
}
}
// ViewModel: 화면의 상태와 로직을 관리하는 클래스
class UserProfileViewModel {
final GetUserDataUseCase useCase;
User? user;
bool isLoading = false;
UserProfileViewModel({required this.useCase}) {
loadUser(); // 생성자에서 데이터 로드
}
void loadUser() async {
isLoading = true;
user = await useCase.execute('123');
isLoading = false;
}
}
// user_profile_page.dart 파일
final repository = UserRepository();
final useCase = GetUserDataUseCase(repository: repository);
final viewModel1 = UserProfileViewModel(useCase: useCase);
// user_detail_page.dart 파일에서도 ViewModel이 필요하면?
final repository2 = UserRepository(); // 똑같은 코드 반복!
final useCase2 = GetUserDataUseCase(repository: repository2); // 똑같은 코드 반복!
final viewModel2 = UserProfileViewModel(useCase: useCase2);
// user_settings_page.dart 파일에서도?
final repository3 = UserRepository(); // 또 반복!
final useCase3 = GetUserDataUseCase(repository: repository3); // 또 반복!
final viewModel3 = UserProfileViewModel(useCase: useCase3);
// 문제점:
// - 생성 로직이 여러 파일에 분산됨 (user_profile_page.dart, user_detail_page.dart 등)
// - UseCase와 ViewModel 생성 코드가 파일마다 중복됨
// - UseCase 생성 방식 변경 시 모든 파일을 수정해야 함
// - 테스트 시 Mock UseCase로 교체하려면 각 파일마다 수정해야 함
// di_setup.dart 파일 (한 곳에만 작성)
getIt.registerSingleton<UserRepository>(UserRepository());
getIt.registerSingleton<GetUserDataUseCase>(
GetUserDataUseCase(repository: getIt()),
);
getIt.registerFactory<UserProfileViewModel>(
() => UserProfileViewModel(
useCase: getIt<GetUserDataUseCase>(), // UseCase는 Singleton에서 가져옴
),
);
// user_profile_page.dart 파일
final viewModel1 = getIt<UserProfileViewModel>(); // 간단!
// user_detail_page.dart 파일
final viewModel2 = getIt<UserProfileViewModel>(); // 간단! (새로운 인스턴스)
// user_settings_page.dart 파일
final viewModel3 = getIt<UserProfileViewModel>(); // 간단! (또 다른 새로운 인스턴스)
// 장점:
// - 생성 로직이 di_setup.dart 한 곳에만 있음
// - 각 화면 파일에서는 getIt<Type>()만 호출하면 됨
// - UseCase 생성 방식 변경 시 di_setup.dart만 수정하면 됨
// - 테스트 시 di_setup.dart에서 Mock UseCase로 한 번만 교체하면 모든 화면에 적용
// - 매번 새로운 ViewModel 인스턴스 생성 → 각 화면이 독립적인 상태 유지
GetIt은 Flutter/Dart용 의존성 주입(Dependency Injection) 컨테이너 라이브러리다.
final getIt = GetIt.instance;
// 객체 등록
getIt.registerSingleton<UserRepository>(UserRepositoryImpl());
getIt.registerFactory<UserProfileViewModel>(
() => UserProfileViewModel(useCase: getIt()),
);
// 객체 가져오기
final repository = getIt<UserRepository>();
final viewModel = getIt<UserProfileViewModel>();
getIt.registerFactory<UserProfileViewModel>(
() => UserProfileViewModel(
useCase: getIt<GetUserDataUseCase>(),
),
);
getIt<UserProfileViewModel>() 호출 시마다 새 인스턴스 생성// 만약 Singleton으로 등록했다면?
getIt.registerSingleton<UserProfileViewModel>(
UserProfileViewModel(useCase: getIt()),
);
// 화면1에서
final viewModel1 = getIt<UserProfileViewModel>();
viewModel1.user = User(name: 'Alice'); // user 상태 변경
// 화면2에서 (같은 인스턴스!)
final viewModel2 = getIt<UserProfileViewModel>();
print(viewModel2.user?.name); // 'Alice' 출력! (화면1의 상태와 공유됨)
// 문제: 화면1의 상태가 화면2에도 영향을 줌 ❌
Factory를 사용하면:
loadUser() 호출 → 각 화면이 독립적으로 데이터 로드getIt.registerSingleton<UserRepository>(UserRepositoryImpl())
getIt.registerSingleton<GetUserDataUseCase>(
GetUserDataUseCase(repository: getIt()),
)
getIt.registerFactory<UserProfileViewModel>(
() => UserProfileViewModel(useCase: getIt()),
)
class CartNotifier extends ChangeNotifier {
void addItem() {
notifyListeners(); // 상태 변경 알림
}
}
| registerSingleton | registerFactory | |
|---|---|---|
| 인스턴스 | 앱 전체 단일 인스턴스 | 호출마다 새 인스턴스 |
| 등록 방식 | 직접 인스턴스 또는 Factory function | Factory function만 |
| 사용 예 | Repository, UseCase, Service | ViewModel, Cart, Session |
| 생명주기 | 앱 시작 시 생성 (또는 지연) | 필요 시 생성 |
| 상태 공유 | 상태가 모든 곳에서 공유됨 | 각 인스턴스가 독립적인 상태 |