GetIt 사용 중 궁금증 해소: Factory Function 이란?

shin_stealer·2026년 1월 4일

Factory Function은 Factory Pattern (디자인 패턴 분류 GoF 23가지 중 하나) 을 구현한 방법 중 하나다.

Factory Pattern

  • 객체 생성 로직을 캡슐화하는 디자인 패턴
  • Factory function은 Factory pattern을 구현하는 방법 중 하나

Factory Function

  • 객체를 생성해서 반환하는 함수
  • 생성자를 직접 호출하지 않고 함수로 감싸서 생성
  • 예: () => ViewModel(useCase: getIt())

Factory Pattern 사용 예시

기본 클래스 정의

// 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;
  }
}

Factory Pattern 사용 안 할 때

// 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로 교체하려면 각 파일마다 수정해야 함

Factory Pattern 사용할 때

// 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이란?

GetIt은 Flutter/Dart용 의존성 주입(Dependency Injection) 컨테이너 라이브러리다.

주요 기능

  • 객체 생성을 중앙에서 관리
  • Singleton, Factory 등 다양한 생명주기 지원
  • 의존성을 자동으로 주입
  • 테스트 시 Mock 객체로 쉽게 교체 가능

기본 사용법

final getIt = GetIt.instance;

// 객체 등록
getIt.registerSingleton<UserRepository>(UserRepositoryImpl());
getIt.registerFactory<UserProfileViewModel>(
  () => UserProfileViewModel(useCase: getIt()),
);

// 객체 가져오기
final repository = getIt<UserRepository>();
final viewModel = getIt<UserProfileViewModel>();

GetIt의 registerFactory

getIt.registerFactory<UserProfileViewModel>(
  () => UserProfileViewModel(
    useCase: getIt<GetUserDataUseCase>(),
  ),
);
  • Factory function을 등록
  • getIt<UserProfileViewModel>() 호출 시마다 새 인스턴스 생성
  • Singleton과 달리 매번 새로운 객체 반환

왜 ViewModel은 Factory를 사용해야 하나?

// 만약 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를 사용하면:

  • 각 화면마다 새로운 ViewModel 인스턴스 생성
  • 화면1과 화면2가 서로 독립적인 상태 유지 ✅
  • 생성자에서 loadUser() 호출 → 각 화면이 독립적으로 데이터 로드

실제 사용 사례

1. Singleton Pattern

getIt.registerSingleton<UserRepository>(UserRepositoryImpl())
getIt.registerSingleton<GetUserDataUseCase>(
  GetUserDataUseCase(repository: getIt()),
)
  • Repository, UseCase 등 앱 전체에서 하나만 필요한 경우
  • 데이터를 공유하거나 재사용해야 하는 경우

2. Factory Method Pattern

getIt.registerFactory<UserProfileViewModel>(
  () => UserProfileViewModel(useCase: getIt()),
)
  • 화면별로 독립적인 ViewModel이 필요한 경우
  • 각 화면이 독립적인 상태를 가져야 하는 경우

3. Observer Pattern

class CartNotifier extends ChangeNotifier {
  void addItem() {
    notifyListeners(); // 상태 변경 알림
  }
}
  • UI 업데이트를 위한 상태 관찰

4. Dependency Injection

  • GetIt으로 의존성 자동 주입
  • 테스트 시 Mock 객체로 교체 용이

Factory vs Singleton in GetIt

registerSingletonregisterFactory
인스턴스앱 전체 단일 인스턴스호출마다 새 인스턴스
등록 방식직접 인스턴스 또는 Factory functionFactory function만
사용 예Repository, UseCase, ServiceViewModel, Cart, Session
생명주기앱 시작 시 생성 (또는 지연)필요 시 생성
상태 공유상태가 모든 곳에서 공유됨각 인스턴스가 독립적인 상태

핵심 정리

  1. Factory Function: 객체 생성 함수
  2. Factory Pattern: 생성 로직 캡슐화 디자인 패턴
  3. Factory vs Singleton: 생명주기 차이로 구분
  4. GetIt: Factory는 지연 생성, Singleton은 즉시 생성 가능
  5. 언제 사용?: 매번 새 인스턴스가 필요한 경우 → Factory 사용
profile
I am a Blacksmith.

0개의 댓글