SlideShare a Scribd company logo
[Flutter] Dependency
Injection과 Service Locator
임태규
• 정보관리 기술사
• 11년차 모바일 엔지니어
(안드로이드, 플러터)
• 전) 삼성전자
• 전) 쿠팡
• 현) Presto Labs
• https://github.com/dualcoder-pe
• https://www.linkedin.com/in/taekyu-lim-b3b629187
Presto Labs
• Global Top Tier High Frequency Trading Firm
• Singapore, Shanghai, Seoul + Remote (Global)
• https://aqx.com
• Mobile, BE, FE 채용 (링크)
목차
Dependency Injection과 Service Locator
Flutter에서 Dependency Injection과 Service Locator
1
2
Dependency Injection과
Service Locator
Part 1
논의의 소재
객체지향 프로그래밍
SOLID
객체
클래스
캡슐화
추상화
다형성
Dependency Injection
Service Locator
IoC
객체지향 프로그래밍
• 객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로
보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 객체들의
모임으로 파악하고자 하는 것이다.
객체지향 프로그래밍
애플리케이션
객체 객체 객체
객체들을 잘 모으려면
어떻게 해야 할까?
응집도는 높게, 결합도는 낮게
SOLID
IoC
Dependency Injection
Service Locator
예제 프로젝트 구조
예제 프로젝트 구조
App Bloc UseCase Repository
Main View DataSource
응집도와 결합도
• 응집도(cohesion)
• 모듈 내부 구성요소 간 연관 정도
• 응집도가 높다 ➔ 모듈 내 모든 기능이 단일 목적을 위해 수행됨
• 결합도(coupling)
• 모듈과 외부 모듈의 연관 정도
• 결합도가 낮다 ➔ 모듈이 기능 수행을 위해 다른 모듈에 의존하지 않음
class SendOrderUsecase {
final OrderRepository _orderRepository = OrderRepository();
Future<OrderResult> sendOrder(Order order) =>
_orderRepository.sendOrder(order);
}
높은 결합도
예제 프로젝트 구조
App Bloc UseCase Repository
Main View DataSource
class OrderRepository {
final LocalDatasource _localDatasource = LocalDatasource();
final RemoteDatesource _remoteDatesource = RemoteDatesource();
Future<OrderResult> sendOrder(Order order) async {
final localRes = await _localDatasource.sendOrder(order);
final remoteRes = await _remoteDatesource.sendOrder(order);
return OrderResult((localRes && remoteRes) ? "SUCCESS" : "Fail");
}
}
높은 결합도
DIP 원칙 적용
SOLID
• Single Responsibility Principle: 단일 책임 원칙
• Open Closed Principle: 개방 폐쇄 원칙
• Liskov Substitution Principle: 리스코프 치환 원칙
• Interface Segregation Principle: 인터페이스 분리 원칙
• Dependency Inversion Principle: 의존성 역전 원칙
➔ 구체적인 대상이 아니라, 추상적인 대상에 의존해야 함
DIP 적용
UseCase Repository DataSource UseCase
Repository
DataSource
Repository
Impl
class OrderRepository {
final LocalDatasource _localDatasource = LocalDatasource();
final RemoteDatesource _remoteDatesource = RemoteDatesource();
Future<OrderResult> sendOrder(Order order) async {
}
}
abstract class OrderRepository {
Future<OrderResult> sendOrder(Order order);
}
DIP 적용
DIP 적용
class OrderRepositoryImpl implements OrderRepository {
final LocalDatasource _localDatasource = LocalDatasource();
final RemoteDatesource _remoteDatesource = RemoteDatesource();
Future<OrderResult> sendOrder(Order order) async {
final localRes = await _localDatasource.sendOrder(order);
final remoteRes = await _remoteDatesource.sendOrder(order);
return OrderResult((localRes && remoteRes) ? "SUCCESS" : "Fail");
}
}
class SendOrderUsecase {
final OrderRepository _orderRepository = OrderRepositoryImpl();
Future<OrderResult> sendOrder(Order order) =>
_orderRepository.sendOrder(order);
}
DIP 적용
아직 구체적인 대상에 대한
의존성을 제거하지 못함
DIP 적용
UseCase Repository DataSource UseCase
Repository
DataSource
Repository
Impl
IoC 원칙 적용
IoC
• 애플리케이션의 제어권을 개발자가 갖지 않고 외부에 위임
• Ex) 안드로이드에서 Lifecycle 별로 프레임워크에서 callback 함수를
호출
IoC 적용을 위한 패턴
• Template Method
• Strategy
• Factory Method
• Abstract Factory
• Dependency Injection
• Service Locator
➔ Interface 구현체와, Interface를 사용하는 App Code를 분리
Dependency Injection
• 의존성 주입
• 객체가 의존성을 외부에서 전달받는 패턴
Dependency Injection
UseCase
Repository
DataSource
Repository
Impl
누군가
• 의존성 주입 유형
• 생성자 주입
• Setter 주입
• 인터페이스 주입
Dependency Injection
• 객체의 생성자를 통해 외부에서 의존성을 주입
생성자 주입
class SendOrderUsecase {
final OrderRepository _orderRepository;
SendOrderUsecase(this._orderRepository);
Future<OrderResult> sendOrder(Order order) =>
_orderRepository.sendOrder(order);
}
class SendOrderUsecase {
late OrderRepository _orderRepository;
set orderRepository(OrderRepository orderRepository) =>
_orderRepository = orderRepository;
Future<OrderResult> sendOrder(Order order) =>
_orderRepository.sendOrder(order);
}
• 객체를 생성한 뒤, 인스턴스의 setter를 통해 의존성을 주입
Setter 주입
class SendOrderUsecase {
OrderRepository? _orderRepository;
set orderRepository(OrderRepository orderRepository) =>
_orderRepository = orderRepository;
Future<OrderResult> sendOrder(Order order) =>
_orderRepository?.sendOrder(order) ?? Future.value(OrderResult("Fail"));
}
• 객체를 생성한 뒤, 인스턴스의 setter를 통해 의존성을 주입
Setter 주입
누군가가 누군가?
(주입은 누가 해주나?)
생성자 주입 – Pure DI
void main() {
runApp(
OrderApp(orderBloc:
OrderBloc(SendOrderUsecase(OrderRepositoryImpl()))));
}
Setter 주입 - Pure DI
void main() {
runApp(OrderApp(
orderBloc: OrderBloc(
SendOrderUsecase()..orderRepository = OrderRepositoryImpl())));
}
• 원인
• Composition Root가 Entry Point와 가까워야 함
• 한계
• 의존관계 tree의 depth가 깊을 때, 안쪽까지 객체를 전달하기 불편함
Pure DI의 한계
App Bloc UseCase
Main View DataSource
Repository
Repository
Impl
누군가
가까이
Composition Root
Entry Point
Depth
Service Locator
• 애플리케이션에 필요한 모든 객체를 알고 있는 객체를 만들고, 그
객체를 통해 의존성을 획득하는 방법
Service Locator
App Bloc UseCase
Main View DataSource
Repository
Repository
Impl
누군가
Composition Root
Entry Point
어딘가
Service Locator
• Service Locator의 유형
• Static Service Locator
• Dynamic Service Locator
Service Locator
class ServiceLocator {
static ServiceLocator? _instance;
final OrderRepository _orderRepository;
ServiceLocator(this._orderRepository);
static void load(ServiceLocator serviceLocator) => _instance = serviceLocator;
static OrderRepository getOrderRepository() => _instance!._orderRepository;
}
Service Locator 구현
Service Locator 객체 생성
void main() {
ServiceLocator.load(ServiceLocator(OrderRepositoryImpl()));
runApp(OrderApp(orderBloc: OrderBloc(SendOrderUsecase())));
}
class SendOrderUsecase {
final OrderRepository _orderRepository = ServiceLocator.getOrderRepository();
Future<OrderResult> sendOrder(Order order) =>
_orderRepository.sendOrder(order);
}
Service Locator 의존성 획득
Dynamic Service Locator 구현
class DynamicServiceLocator {
static DynamicServiceLocator? _instance;
final HashMap<String, dynamic> _services = HashMap();
static void load(DynamicServiceLocator serviceLocator) =>
_instance = serviceLocator;
void loadService(String key, dynamic object) => _services[key] = object;
static dynamic getService(String key) => _instance!._services[key];
}
void main() {
DynamicServiceLocator locator = DynamicServiceLocator();
locator.loadService("OrderRepository", OrderRepositoryImpl());
DynamicServiceLocator.load(locator);
runApp(OrderApp(orderBloc: OrderBloc(SendOrderUsecase())));
}
Dynamic Service Locator 객체 생성
class SendOrderUsecase {
final OrderRepository _orderRepository =
DynamicServiceLocator.getService("OrderRepository");
Future<OrderResult> sendOrder(Order order) =>
_orderRepository.sendOrder(order);
}
Dynamic Service Locator 의존성 획득
Service Locator는
안티 패턴이다
- mark seemann -
• 종속성을 숨김
• 단위테스트 어려움
• mocking이 가능하더라도, 전역 메모리 사용하므로, 테스트 후 clear 필요
• 전역 싱글톤 패턴
• 구현 상 전역 싱글톤을 안쓰더라도, 내부적으로 공유 메모리 사용을 피할 수 없음)
• 모든 클래스가 로케이터와 커플링
• Runtime Error
• Locator의 존재를 모르고 생성을 안하고 사용하면 런타임 에러
• 의존 객체를 찾기 어려움
• 내부에 의존성 추가되면 컴파일러가 알 수 없음
• 캡슐화 위반
• 객체 사용을 위한 전제 조건을 알 수 없음
Service Locator의 단점
Flutter의 DI 라이브러리
• GetX
• GetIt
• InheritedWidget
• Provider/Riverpod
➔ InheritedWidget, Riverpod은 상태관리
Flutter DI 라이브러리
void main() {
Get.lazyPut<OrderRepository>(() => OrderRepositoryImpl());
runApp(OrderApp(orderBloc: OrderBloc(SendOrderUsecase())));
}
GetX의 Injection 방식
GetX의 Injection 방식
class SendOrderUsecase {
final OrderRepository _orderRepository = Get.find();
Future<OrderResult> sendOrder(Order order) =>
_orderRepository.sendOrder(order);
}
DI vs GetX
DI와 GetX의 비교 – DI
class OrderApp extends StatelessWidget {
const OrderApp({Key? key, required this.orderBloc}) : super(key: key);
final OrderBloc orderBloc;
class OrderBloc {
final SendOrderUsecase _sendOrderUsecase;
OrderBloc(this._sendOrderUsecase);
DI와 GetX의 비교 – DI
class SendOrderUsecase {
final OrderRepository _orderRepository;
SendOrderUsecase(this._orderRepository);
DI와 GetX의 비교 – DI
class OrderRepositoryImpl implements OrderRepository {
final LocalDatasource _localDatasource;
final RemoteDatasource _remoteDatesource;
OrderRepositoryImpl(this._localDatasource, this._remoteDatesource);
DI와 GetX의 비교 – DI
void main() {
runApp(OrderApp(
orderBloc: OrderBloc(SendOrderUsecase(
OrderRepositoryImpl(LocalDatasource(), RemoteDatasource())))));
}
DI와 GetX의 비교 – DI
class OrderApp extends StatelessWidget {
OrderApp({Key? key}) : super(key: key);
final OrderBloc orderBloc = Get.find();
DI와 GetX의 비교 – GetX
DI와 GetX의 비교 – GetX
class OrderBloc {
final SendOrderUsecase _sendOrderUsecase = Get.find();
class SendOrderUsecase {
final OrderRepository _orderRepository = Get.find();
class OrderRepositoryImpl implements OrderRepository {
final LocalDatasource _localDatasource = Get.find();
final RemoteDatasource _remoteDatesource = Get.find();
void main() {
Get.lazyPut(() => OrderBloc());
Get.lazyPut(() => SendOrderUsecase());
Get.lazyPut<OrderRepository>(() => OrderRepositoryImpl());
Get.lazyPut(() => LocalDatasource());
Get.lazyPut(() => RemoteDatasource());
runApp(OrderApp());
}
DI와 GetX의 비교 – GetX
DI와 GetX의 비교
App Bloc UseCase
Main View DataSource
Repository
Repository
Impl
누군가
Composition Root
Entry Point
Service Locator
누군가 어딘가
Composition Root
DI
GetX
GetX는 Service Locator
패턴
GetX를 Service Locator로만
쓸 수 있을까?
GetX를 DI Container로 활용하기
App Bloc UseCase
Main View DataSource
Repository
Repository
Impl
누군가
Composition Root
Entry Point
Service Locator
누군가 어딘가
Composition Root
DI with GetX
GetX
어딘가
Get
• DI는 원칙과 패턴
• 지금까지 본 DI는 Pure DI
• 외부 라이브러리나 객체 사용 없이 의존성 주입
• DI Container
• 의존성 주입할 모든 객체를 관리하는 모듈
• 역할 (RRR)
• Register: 인스턴스 등록
• Resolve: 인스턴스 제공
• Release: 인스턴스 해제
Pure DI와 DI Container
class Injector {
static void register() {
Get.lazyPut(() => LocalDatasource());
Get.lazyPut(() => RemoteDatasource());
Get.lazyPut<OrderRepository>(
() => OrderRepositoryImpl(Get.find(), Get.find()));
Get.lazyPut(() => SendOrderUsecase(Get.find()));
Get.lazyPut(() => OrderBloc(Get.find()));
}
}
GetX를 DI Container로 활용하기
GetX를 DI Container로 활용하기
void main() {
Injector.register();
runApp(OrderApp(orderBloc: Get.find()));
}
GetX를 DI Container로 활용하기
class OrderApp extends StatelessWidget {
const OrderApp({Key? key, required this.orderBloc}) : super(key: key);
final OrderBloc orderBloc;
class OrderBloc {
final SendOrderUsecase _sendOrderUsecase;
OrderBloc(this._sendOrderUsecase);
GetX를 DI Container로 활용하기
class SendOrderUsecase {
final OrderRepository _orderRepository;
SendOrderUsecase(this._orderRepository);
GetX를 DI Container로 활용하기
class OrderRepositoryImpl implements OrderRepository {
final LocalDatasource _localDatasource;
final RemoteDatasource _remoteDatesource;
OrderRepositoryImpl(this._localDatasource, this._remoteDatesource);
GetX를 DI Container로 활용하기
• 장점
• GetX의 Memory Management 기능 활용 가능
• Entry Point에서 가까운 Composition Root에서 의존관계 관리
• 의존관계 tree의 depth가 깊어도, 안쪽까지 객체를 쉽게 전달
• 단점
• GetX에 등록을 잊는다면, 여전히 Runtime Error 발생 가능
GetX를 DI Container로 활용하기
예외적으로 Composition Root가
아닌 곳에서 주입하고 싶다면?
class SendOrderUsecase {
final OrderRepository _orderRepository;
SendOrderUsecase([OrderRepository? orderRepository])
: _orderRepository = orderRepository ?? Get.find();
Future<OrderResult> sendOrder(Order order) =>
_orderRepository.sendOrder(order);
}
GetX를 DI Container로 활용하기
• 예외적으로 생성자에서 객체 생성해 주입 허용
• 외부에 의존관계 알림
• 테스트 용이
• 객체 주입 용이
• 단, 모든 객체가 GetX 의존성 가짐
GetX를 DI Container로 활용하기
• 원인
• 객체를 생성 및 조립한 후 변경이 불가능함
• 한계
• 객체 생성에 Runtime 요소가 필요할 때 대응이 어려움
• 보완
• 추상 팩토리 메서드 패턴 활용
한계 및 보완
• 의존성 있는 객체간 결합도를 낮추는 방법
• DI: 생성자 주입, Setter 주입
• Service Locator: 의존성 필요한 객체가 컨테이너에 객체 요청
• Service Locator는 안티패턴이라는 주장 (Mark Seemann)
• Flutter의 의존성 주입 (GetX, GetIT) ➔ Service Locator 방식
• GetX, GetIt을 DI Container로 활용할 수 있음
• 객체 생성에 Runtime 요소가 필요할 경우, 추상 팩토리 패턴으로 보완
정리
Q & A

More Related Content

PDF
[2022]Flutter_IO_Extended_Korea_멀티모듈을활용한플러터클린아키텍처_...
PDF
Google Firebase presentation - English
PDF
Google I/O Extended Seoul 2023 Flutter 에 Clean Architecture 를 얹어보자 - 2/e
PPTX
React Native
PDF
React Native
PPTX
Introduction to Firebase
PPTX
Firebase
PPT
MVC ppt presentation
[2022]Flutter_IO_Extended_Korea_멀티모듈을활용한플러터클린아키텍처_...
Google Firebase presentation - English
Google I/O Extended Seoul 2023 Flutter 에 Clean Architecture 를 얹어보자 - 2/e
React Native
React Native
Introduction to Firebase
Firebase
MVC ppt presentation

What's hot (20)

PDF
Introduction to react-query. A Redux alternative? (Nikos Kleidis, Front End D...
PPTX
Node.js Socket.IO
PPT
7. Key-Value Databases: In Depth
PDF
Blockchains and databases a new era in distributed computing
PDF
Beyond Java: 자바 8을 중심으로 본 자바의 혁신
PDF
Single Page Applications
PDF
Let'Swift 2023 iOS 애플리케이션 개발 생산성 고찰
- 정시 퇴근을 위해 우리는 어떻게 해야할 것인가?
PPSX
Domain Driven Design
PPTX
Spring Framework Petclinic sample application
PDF
Maxwell's Daemon
PDF
PPTX
Abstract Factory Design Pattern
PDF
Native mobile application development with Flutter (Dart)
PDF
#살아있다 #자프링외길12년차 #코프링2개월생존기
PDF
[236] 카카오의데이터파이프라인 윤도영
PPTX
bigquery.pptx
PPTX
Introduction to React
PPTX
React workshop presentation
PDF
WEB DEVELOPMENT USING REACT JS
PDF
Asynchronous API in Java8, how to use CompletableFuture
Introduction to react-query. A Redux alternative? (Nikos Kleidis, Front End D...
Node.js Socket.IO
7. Key-Value Databases: In Depth
Blockchains and databases a new era in distributed computing
Beyond Java: 자바 8을 중심으로 본 자바의 혁신
Single Page Applications
Let'Swift 2023 iOS 애플리케이션 개발 생산성 고찰
- 정시 퇴근을 위해 우리는 어떻게 해야할 것인가?
Domain Driven Design
Spring Framework Petclinic sample application
Maxwell's Daemon
Abstract Factory Design Pattern
Native mobile application development with Flutter (Dart)
#살아있다 #자프링외길12년차 #코프링2개월생존기
[236] 카카오의데이터파이프라인 윤도영
bigquery.pptx
Introduction to React
React workshop presentation
WEB DEVELOPMENT USING REACT JS
Asynchronous API in Java8, how to use CompletableFuture
Ad

Similar to [2022]NaverMeetup_[Flutter] Dependency Injection과 Service Locator_임태규.pdf (20)

PPTX
Dependency Injection 소개
PDF
Spring Framework - Inversion of Control Container
PPTX
3.Spring IoC&DI(spring ioc실습, XML기반)
PDF
(자바교육/스프링교육/스프링프레임워크교육/마이바티스교육추천)#2.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)
PPTX
NCS기반 Spring Framework & MyBatis_ 스프링프레임워크 & 마이바티스 ☆무료강의자료 제공/ 구로오라클학원, 탑크리에...
PPTX
NCS기반 Spring Framework & MyBatis_ 스프링프레임워크 & 마이바티스 ☆무료강의자료 제공/ 구로오라클학원, 탑크리에...
PDF
객체지향 설계
PPTX
프로그래밍 패러다임의 진화 및 Spring의 금융권 적용
PDF
DDD 구현기초 (거의 Final 버전)
PDF
2023.06.12 발표 자료 : JPA / 스프링 구조
PPTX
Spring IoC
PDF
My di container
PDF
[NEXT] 화면 재갱신이 되는 안드로이드 앱 만들기 - 네트워크에 독립하는 구조로 변경
PPTX
[스프링 스터디 2일차] IoC 컨테이너와 DI
PPTX
DI - Dependency Injection
PPTX
멋쟁이사자처럼 백엔드_스프링_강의자료_스프링에서 찾아볼 수 있는 객체지향
PPTX
스프링프레임워크 & 마이바티스 무.료 강의자료 제공 (Spring IoC & DI)_ 구로자바학원/구로오라클학원/구로IT학원
PPTX
Sql 중심 코드 탈피
PPTX
[스프링 스터디 1일차] 오브젝트와 의존관계
Dependency Injection 소개
Spring Framework - Inversion of Control Container
3.Spring IoC&DI(spring ioc실습, XML기반)
(자바교육/스프링교육/스프링프레임워크교육/마이바티스교육추천)#2.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)
NCS기반 Spring Framework & MyBatis_ 스프링프레임워크 & 마이바티스 ☆무료강의자료 제공/ 구로오라클학원, 탑크리에...
NCS기반 Spring Framework & MyBatis_ 스프링프레임워크 & 마이바티스 ☆무료강의자료 제공/ 구로오라클학원, 탑크리에...
객체지향 설계
프로그래밍 패러다임의 진화 및 Spring의 금융권 적용
DDD 구현기초 (거의 Final 버전)
2023.06.12 발표 자료 : JPA / 스프링 구조
Spring IoC
My di container
[NEXT] 화면 재갱신이 되는 안드로이드 앱 만들기 - 네트워크에 독립하는 구조로 변경
[스프링 스터디 2일차] IoC 컨테이너와 DI
DI - Dependency Injection
멋쟁이사자처럼 백엔드_스프링_강의자료_스프링에서 찾아볼 수 있는 객체지향
스프링프레임워크 & 마이바티스 무.료 강의자료 제공 (Spring IoC & DI)_ 구로자바학원/구로오라클학원/구로IT학원
Sql 중심 코드 탈피
[스프링 스터디 1일차] 오브젝트와 의존관계
Ad

[2022]NaverMeetup_[Flutter] Dependency Injection과 Service Locator_임태규.pdf

  • 2. 임태규 • 정보관리 기술사 • 11년차 모바일 엔지니어 (안드로이드, 플러터) • 전) 삼성전자 • 전) 쿠팡 • 현) Presto Labs • https://github.com/dualcoder-pe • https://www.linkedin.com/in/taekyu-lim-b3b629187
  • 3. Presto Labs • Global Top Tier High Frequency Trading Firm • Singapore, Shanghai, Seoul + Remote (Global) • https://aqx.com • Mobile, BE, FE 채용 (링크)
  • 4. 목차 Dependency Injection과 Service Locator Flutter에서 Dependency Injection과 Service Locator 1 2
  • 7. 객체지향 프로그래밍 • 객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 객체들의 모임으로 파악하고자 하는 것이다.
  • 10. 응집도는 높게, 결합도는 낮게 SOLID IoC Dependency Injection Service Locator
  • 12. 예제 프로젝트 구조 App Bloc UseCase Repository Main View DataSource
  • 13. 응집도와 결합도 • 응집도(cohesion) • 모듈 내부 구성요소 간 연관 정도 • 응집도가 높다 ➔ 모듈 내 모든 기능이 단일 목적을 위해 수행됨 • 결합도(coupling) • 모듈과 외부 모듈의 연관 정도 • 결합도가 낮다 ➔ 모듈이 기능 수행을 위해 다른 모듈에 의존하지 않음
  • 14. class SendOrderUsecase { final OrderRepository _orderRepository = OrderRepository(); Future<OrderResult> sendOrder(Order order) => _orderRepository.sendOrder(order); } 높은 결합도
  • 15. 예제 프로젝트 구조 App Bloc UseCase Repository Main View DataSource
  • 16. class OrderRepository { final LocalDatasource _localDatasource = LocalDatasource(); final RemoteDatesource _remoteDatesource = RemoteDatesource(); Future<OrderResult> sendOrder(Order order) async { final localRes = await _localDatasource.sendOrder(order); final remoteRes = await _remoteDatesource.sendOrder(order); return OrderResult((localRes && remoteRes) ? "SUCCESS" : "Fail"); } } 높은 결합도
  • 18. SOLID • Single Responsibility Principle: 단일 책임 원칙 • Open Closed Principle: 개방 폐쇄 원칙 • Liskov Substitution Principle: 리스코프 치환 원칙 • Interface Segregation Principle: 인터페이스 분리 원칙 • Dependency Inversion Principle: 의존성 역전 원칙 ➔ 구체적인 대상이 아니라, 추상적인 대상에 의존해야 함
  • 19. DIP 적용 UseCase Repository DataSource UseCase Repository DataSource Repository Impl
  • 20. class OrderRepository { final LocalDatasource _localDatasource = LocalDatasource(); final RemoteDatesource _remoteDatesource = RemoteDatesource(); Future<OrderResult> sendOrder(Order order) async { } } abstract class OrderRepository { Future<OrderResult> sendOrder(Order order); } DIP 적용
  • 21. DIP 적용 class OrderRepositoryImpl implements OrderRepository { final LocalDatasource _localDatasource = LocalDatasource(); final RemoteDatesource _remoteDatesource = RemoteDatesource(); Future<OrderResult> sendOrder(Order order) async { final localRes = await _localDatasource.sendOrder(order); final remoteRes = await _remoteDatesource.sendOrder(order); return OrderResult((localRes && remoteRes) ? "SUCCESS" : "Fail"); } }
  • 22. class SendOrderUsecase { final OrderRepository _orderRepository = OrderRepositoryImpl(); Future<OrderResult> sendOrder(Order order) => _orderRepository.sendOrder(order); } DIP 적용 아직 구체적인 대상에 대한 의존성을 제거하지 못함
  • 23. DIP 적용 UseCase Repository DataSource UseCase Repository DataSource Repository Impl
  • 25. IoC • 애플리케이션의 제어권을 개발자가 갖지 않고 외부에 위임 • Ex) 안드로이드에서 Lifecycle 별로 프레임워크에서 callback 함수를 호출
  • 26. IoC 적용을 위한 패턴 • Template Method • Strategy • Factory Method • Abstract Factory • Dependency Injection • Service Locator ➔ Interface 구현체와, Interface를 사용하는 App Code를 분리
  • 28. • 의존성 주입 • 객체가 의존성을 외부에서 전달받는 패턴 Dependency Injection UseCase Repository DataSource Repository Impl 누군가
  • 29. • 의존성 주입 유형 • 생성자 주입 • Setter 주입 • 인터페이스 주입 Dependency Injection
  • 30. • 객체의 생성자를 통해 외부에서 의존성을 주입 생성자 주입 class SendOrderUsecase { final OrderRepository _orderRepository; SendOrderUsecase(this._orderRepository); Future<OrderResult> sendOrder(Order order) => _orderRepository.sendOrder(order); }
  • 31. class SendOrderUsecase { late OrderRepository _orderRepository; set orderRepository(OrderRepository orderRepository) => _orderRepository = orderRepository; Future<OrderResult> sendOrder(Order order) => _orderRepository.sendOrder(order); } • 객체를 생성한 뒤, 인스턴스의 setter를 통해 의존성을 주입 Setter 주입
  • 32. class SendOrderUsecase { OrderRepository? _orderRepository; set orderRepository(OrderRepository orderRepository) => _orderRepository = orderRepository; Future<OrderResult> sendOrder(Order order) => _orderRepository?.sendOrder(order) ?? Future.value(OrderResult("Fail")); } • 객체를 생성한 뒤, 인스턴스의 setter를 통해 의존성을 주입 Setter 주입
  • 34. 생성자 주입 – Pure DI void main() { runApp( OrderApp(orderBloc: OrderBloc(SendOrderUsecase(OrderRepositoryImpl())))); }
  • 35. Setter 주입 - Pure DI void main() { runApp(OrderApp( orderBloc: OrderBloc( SendOrderUsecase()..orderRepository = OrderRepositoryImpl()))); }
  • 36. • 원인 • Composition Root가 Entry Point와 가까워야 함 • 한계 • 의존관계 tree의 depth가 깊을 때, 안쪽까지 객체를 전달하기 불편함 Pure DI의 한계 App Bloc UseCase Main View DataSource Repository Repository Impl 누군가 가까이 Composition Root Entry Point Depth
  • 38. • 애플리케이션에 필요한 모든 객체를 알고 있는 객체를 만들고, 그 객체를 통해 의존성을 획득하는 방법 Service Locator App Bloc UseCase Main View DataSource Repository Repository Impl 누군가 Composition Root Entry Point 어딘가 Service Locator
  • 39. • Service Locator의 유형 • Static Service Locator • Dynamic Service Locator Service Locator
  • 40. class ServiceLocator { static ServiceLocator? _instance; final OrderRepository _orderRepository; ServiceLocator(this._orderRepository); static void load(ServiceLocator serviceLocator) => _instance = serviceLocator; static OrderRepository getOrderRepository() => _instance!._orderRepository; } Service Locator 구현
  • 41. Service Locator 객체 생성 void main() { ServiceLocator.load(ServiceLocator(OrderRepositoryImpl())); runApp(OrderApp(orderBloc: OrderBloc(SendOrderUsecase()))); }
  • 42. class SendOrderUsecase { final OrderRepository _orderRepository = ServiceLocator.getOrderRepository(); Future<OrderResult> sendOrder(Order order) => _orderRepository.sendOrder(order); } Service Locator 의존성 획득
  • 43. Dynamic Service Locator 구현 class DynamicServiceLocator { static DynamicServiceLocator? _instance; final HashMap<String, dynamic> _services = HashMap(); static void load(DynamicServiceLocator serviceLocator) => _instance = serviceLocator; void loadService(String key, dynamic object) => _services[key] = object; static dynamic getService(String key) => _instance!._services[key]; }
  • 44. void main() { DynamicServiceLocator locator = DynamicServiceLocator(); locator.loadService("OrderRepository", OrderRepositoryImpl()); DynamicServiceLocator.load(locator); runApp(OrderApp(orderBloc: OrderBloc(SendOrderUsecase()))); } Dynamic Service Locator 객체 생성
  • 45. class SendOrderUsecase { final OrderRepository _orderRepository = DynamicServiceLocator.getService("OrderRepository"); Future<OrderResult> sendOrder(Order order) => _orderRepository.sendOrder(order); } Dynamic Service Locator 의존성 획득
  • 47. • 종속성을 숨김 • 단위테스트 어려움 • mocking이 가능하더라도, 전역 메모리 사용하므로, 테스트 후 clear 필요 • 전역 싱글톤 패턴 • 구현 상 전역 싱글톤을 안쓰더라도, 내부적으로 공유 메모리 사용을 피할 수 없음) • 모든 클래스가 로케이터와 커플링 • Runtime Error • Locator의 존재를 모르고 생성을 안하고 사용하면 런타임 에러 • 의존 객체를 찾기 어려움 • 내부에 의존성 추가되면 컴파일러가 알 수 없음 • 캡슐화 위반 • 객체 사용을 위한 전제 조건을 알 수 없음 Service Locator의 단점
  • 49. • GetX • GetIt • InheritedWidget • Provider/Riverpod ➔ InheritedWidget, Riverpod은 상태관리 Flutter DI 라이브러리
  • 50. void main() { Get.lazyPut<OrderRepository>(() => OrderRepositoryImpl()); runApp(OrderApp(orderBloc: OrderBloc(SendOrderUsecase()))); } GetX의 Injection 방식
  • 51. GetX의 Injection 방식 class SendOrderUsecase { final OrderRepository _orderRepository = Get.find(); Future<OrderResult> sendOrder(Order order) => _orderRepository.sendOrder(order); }
  • 53. DI와 GetX의 비교 – DI class OrderApp extends StatelessWidget { const OrderApp({Key? key, required this.orderBloc}) : super(key: key); final OrderBloc orderBloc;
  • 54. class OrderBloc { final SendOrderUsecase _sendOrderUsecase; OrderBloc(this._sendOrderUsecase); DI와 GetX의 비교 – DI
  • 55. class SendOrderUsecase { final OrderRepository _orderRepository; SendOrderUsecase(this._orderRepository); DI와 GetX의 비교 – DI
  • 56. class OrderRepositoryImpl implements OrderRepository { final LocalDatasource _localDatasource; final RemoteDatasource _remoteDatesource; OrderRepositoryImpl(this._localDatasource, this._remoteDatesource); DI와 GetX의 비교 – DI
  • 57. void main() { runApp(OrderApp( orderBloc: OrderBloc(SendOrderUsecase( OrderRepositoryImpl(LocalDatasource(), RemoteDatasource()))))); } DI와 GetX의 비교 – DI
  • 58. class OrderApp extends StatelessWidget { OrderApp({Key? key}) : super(key: key); final OrderBloc orderBloc = Get.find(); DI와 GetX의 비교 – GetX
  • 59. DI와 GetX의 비교 – GetX class OrderBloc { final SendOrderUsecase _sendOrderUsecase = Get.find(); class SendOrderUsecase { final OrderRepository _orderRepository = Get.find(); class OrderRepositoryImpl implements OrderRepository { final LocalDatasource _localDatasource = Get.find(); final RemoteDatasource _remoteDatesource = Get.find();
  • 60. void main() { Get.lazyPut(() => OrderBloc()); Get.lazyPut(() => SendOrderUsecase()); Get.lazyPut<OrderRepository>(() => OrderRepositoryImpl()); Get.lazyPut(() => LocalDatasource()); Get.lazyPut(() => RemoteDatasource()); runApp(OrderApp()); } DI와 GetX의 비교 – GetX
  • 61. DI와 GetX의 비교 App Bloc UseCase Main View DataSource Repository Repository Impl 누군가 Composition Root Entry Point Service Locator 누군가 어딘가 Composition Root DI GetX
  • 64. GetX를 DI Container로 활용하기 App Bloc UseCase Main View DataSource Repository Repository Impl 누군가 Composition Root Entry Point Service Locator 누군가 어딘가 Composition Root DI with GetX GetX 어딘가 Get
  • 65. • DI는 원칙과 패턴 • 지금까지 본 DI는 Pure DI • 외부 라이브러리나 객체 사용 없이 의존성 주입 • DI Container • 의존성 주입할 모든 객체를 관리하는 모듈 • 역할 (RRR) • Register: 인스턴스 등록 • Resolve: 인스턴스 제공 • Release: 인스턴스 해제 Pure DI와 DI Container
  • 66. class Injector { static void register() { Get.lazyPut(() => LocalDatasource()); Get.lazyPut(() => RemoteDatasource()); Get.lazyPut<OrderRepository>( () => OrderRepositoryImpl(Get.find(), Get.find())); Get.lazyPut(() => SendOrderUsecase(Get.find())); Get.lazyPut(() => OrderBloc(Get.find())); } } GetX를 DI Container로 활용하기
  • 67. GetX를 DI Container로 활용하기 void main() { Injector.register(); runApp(OrderApp(orderBloc: Get.find())); }
  • 68. GetX를 DI Container로 활용하기 class OrderApp extends StatelessWidget { const OrderApp({Key? key, required this.orderBloc}) : super(key: key); final OrderBloc orderBloc;
  • 69. class OrderBloc { final SendOrderUsecase _sendOrderUsecase; OrderBloc(this._sendOrderUsecase); GetX를 DI Container로 활용하기
  • 70. class SendOrderUsecase { final OrderRepository _orderRepository; SendOrderUsecase(this._orderRepository); GetX를 DI Container로 활용하기
  • 71. class OrderRepositoryImpl implements OrderRepository { final LocalDatasource _localDatasource; final RemoteDatasource _remoteDatesource; OrderRepositoryImpl(this._localDatasource, this._remoteDatesource); GetX를 DI Container로 활용하기
  • 72. • 장점 • GetX의 Memory Management 기능 활용 가능 • Entry Point에서 가까운 Composition Root에서 의존관계 관리 • 의존관계 tree의 depth가 깊어도, 안쪽까지 객체를 쉽게 전달 • 단점 • GetX에 등록을 잊는다면, 여전히 Runtime Error 발생 가능 GetX를 DI Container로 활용하기
  • 73. 예외적으로 Composition Root가 아닌 곳에서 주입하고 싶다면?
  • 74. class SendOrderUsecase { final OrderRepository _orderRepository; SendOrderUsecase([OrderRepository? orderRepository]) : _orderRepository = orderRepository ?? Get.find(); Future<OrderResult> sendOrder(Order order) => _orderRepository.sendOrder(order); } GetX를 DI Container로 활용하기
  • 75. • 예외적으로 생성자에서 객체 생성해 주입 허용 • 외부에 의존관계 알림 • 테스트 용이 • 객체 주입 용이 • 단, 모든 객체가 GetX 의존성 가짐 GetX를 DI Container로 활용하기
  • 76. • 원인 • 객체를 생성 및 조립한 후 변경이 불가능함 • 한계 • 객체 생성에 Runtime 요소가 필요할 때 대응이 어려움 • 보완 • 추상 팩토리 메서드 패턴 활용 한계 및 보완
  • 77. • 의존성 있는 객체간 결합도를 낮추는 방법 • DI: 생성자 주입, Setter 주입 • Service Locator: 의존성 필요한 객체가 컨테이너에 객체 요청 • Service Locator는 안티패턴이라는 주장 (Mark Seemann) • Flutter의 의존성 주입 (GetX, GetIT) ➔ Service Locator 방식 • GetX, GetIt을 DI Container로 활용할 수 있음 • 객체 생성에 Runtime 요소가 필요할 경우, 추상 팩토리 패턴으로 보완 정리
  • 78. Q & A