- 教程
- 技术博客
- 文档
【免费下载链接】YCBlogs
技术博客笔记大汇总,包括Java基础,线程,并发,数据结构;Android技术博客等等;常用设计模式;常见的算法;网络协议知识点;部分flutter笔记;还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!转载请注明出处,谢谢!
本篇技术指南围绕 get_it 原理介绍 展开,系统讲解 Flutter 生态中最轻量的服务定位器(ServiceLocator)get_it:它如何用极少的代码实现"接口与实现解耦"、如何通过三种注册方式控制服务生命周期、重复注册时的断言保护机制,以及基于 Map 存储带来的 O(1) 获取性能。读完本文,你将能够在自己的 Flutter 工程中独立搭建一套"注册 + 注入"的依赖管理方案,并理解它背后与依赖倒置原则(DIP)、控制反转(IoC)、依赖注入(DI)之间的思想关联。
01. ServiceLocator 是什么
1.1 核心原理:让调用方只依赖接口
Service Locator(服务定位器)的核心价值在于:将接口(抽象基类)与具体实现分离和解耦合,同时允许通过接口从 App 中的任何位置访问具体实现,从而提升模块的隔离性和可测试性。
用一句话概括它的工作方式:服务不再由使用者创建,而是通过容器注入。这样我们就不再依赖于具体的实现类,而是依赖一层薄薄的接口;调用者无需知道服务的具体实现细节,就可以很轻松地用 Mock 数据替换真实实现。
这一思想与仓库中 依赖倒置原则介绍 中总结的 DIP 完全一致:高层模块不应依赖低层模块,两者都应依赖抽象;抽象不要依赖具体实现细节,具体实现细节依赖抽象。get_it 正是把这条原则落地为代码的容器化实现。
关键认知:ServiceLocator 本质上是一种特殊的控制反转(IoC)。在传统的写法中,对象由使用方主动
new出来(控制权在使用方);引入容器后,对象的创建、装配、生命周期都由容器管理(控制权反转给了容器)。
1.2 get_it:99 行的轻量级实现
get_it 是一个轻量级 ServiceLocator 库,据 get_it 原理介绍 记载,其核心代码(含注释)仅约 99 行。这个数字本身就是它的设计哲学:尽量薄、尽量快、没有魔法。
正因为实现极简,所以建议有时间的话都去阅读一下它的源码。99 行代码意味着你可以在很短的时间内完整理解"注册"与"获取"这两个操作背后发生的全部事情——这在后面分析性能时会有直观的体现。
1.3 与单例模式的对比:为什么需要容器
在引入 ServiceLocator 之前,开发者普遍用单例模式管理全局服务。但仓库 单例模式不友好 一文详细列举了单例的问题:
- 对 OOP 不友好:单例硬编码了
getInstance()调用方式,违背了"基于接口而非实现"的设计原则,未来替换实现时需要改动所有调用点; - 隐藏类之间依赖:单例不需要显式传参,函数内直接调用即可,导致类与类之间的依赖关系在阅读代码时难以一眼看出;
- 可测试性不友好:单例硬编码的使用方式导致无法用 Mock 替换依赖;
- 不支持有参构造函数:无法在创建时灵活传入配置参数。
而 get_it 将"全局唯一"的管理从"类自身"移到"容器",同时保留了接口抽象能力,恰好规避了上述问题:依赖关系通过注册/注入显式可见,测试时只需向容器注册 Mock 实现即可完成替换。
02. 如何快速使用
get_it 的使用非常简单,只有两步:注册服务 + 依赖注入。
2.1 注册服务
第一步,创建一个全局的GetIt实例,并把服务实现注册到容器中:
GetIt getIt = new GetIt(); void register(){ // 注册模式状态管理 service(普通单例,手动初始化) getIt.registerSingleton<BusinessService>(new BusinessServiceImpl()); // 注册懒加载单例(第一次使用时才初始化) getIt.registerLazySingleton<BusinessService>(() => new BusinessServiceImpl()); }这里以BusinessService为接口、BusinessServiceImpl为具体实现为例:注册时指定了泛型BusinessService,容器会把这个类型作为之后查找服务的"钥匙"。
2.2 依赖注入(获取)
第二步,在需要使用该依赖的地方,仍然通过容器来获取:
var businessService = getIt<BusinessService>(); businessService.noneBusinessPattern();由于getIt实例是全局持有的,因此从 App 的任何位置都可以通过getIt<T>()取到对应服务。
2.3 使用流程总结
整个流程可以抽象为:
- 定义接口:如
BusinessService,声明业务方法; - 实现接口:如
BusinessServiceImpl; - 注册进容器:
getIt.registerSingleton<BusinessService>(impl); - 按接口取用:
getIt<BusinessService>(),调用方只与接口打交道。
这样服务在容器中创建,依赖注入(DI)时只依赖接口,调用方不需要知道具体实现类是谁,实现了彻底的解耦。仓库 Flutter状态管理 的 8.3 节就是一个真实的落地案例:页面中通过serviceLocator<BusinessPatternService>()获取服务,再调用_patternService.normalPattern()去驱动 Provider 中的状态变更,服务层与 UI 层因此完全解耦。
03. get_it 不同的注册方式
GetIt 提供了多种注册方式,这些方式决定了被注册对象的生命周期。目前主要有三种:
| 注册方式 | 方法签名 | 实例行为 | 初始化时机 |
|---|---|---|---|
| 工厂模式 | void registerFactory<T>(FactoryFunc<T> func) | 每次获取都返回新实例 | 每次获取时 |
| 单例模式 | void registerSingleton<T>(T instance) | 每次返回同一实例 | 注册时手动创建 |
| 懒加载单例 | void registerLazySingleton<T>(FactoryFunc<T> func) | 每次返回同一实例 | 第一次获取时 |
3.1 Factory 工厂模式
getIt.registerFactory<T>(FactoryFunc<T> func);传入参数是一个函数func,这个函数要返回一个实现了类型T的对象。每次调用获取对象的方式时,都会返回一个新对象,因此适合那些"每次使用都需要独立状态、不能共享"的服务,例如轻量的请求上下文、一次性任务对象等。
3.2 单例模式 & 懒加载单例
单例注册时,传入一个泛型T的对象或其子类的对象(即直接传实例):
getIt.registerSingleton<T>(T instance);懒加载单例则传入一个工厂函数:
getIt.registerLazySingleton<T>(FactoryFunc<T> func);两者的区别在于初始化时机:
- 单例模式在注册时(如 App 启动阶段)就完成初始化;
- 懒加载单例在第一次注入依赖时才初始化服务,之后每次获取返回相同的实例。
实战建议:如果创建这个单例是耗时的(例如初始化数据库连接、加载大配置、创建网络会话),那么可以不要在 App 启动时注册,而放到第一次使用到该服务的时候,即使用懒加载的方式。这样可以显著缩短启动时间,把昂贵对象的创建成本推迟到真正需要它的时刻。
从源码结构看,懒加载的实现思路是:容器内部用一个标识位记录该服务是否已被创建,首次获取时调用工厂函数并缓存结果,之后直接返回缓存;工厂模式则不做缓存,每次都调用工厂函数。这一推断与"懒加载单例返回相同实例、工厂每次返回新实例"的行为完全吻合。
04. 覆盖注册会出现什么
如果你在容器中注册了两次同一服务,默认情况下会在调试模式中得到一个断言(assert):
void setupLocator(){ getIt.registerSingleton(NavigateService()); getIt.registerSingleton(NavigateService()); }运行后会得到类似如下的断言失败信息:
Failed assertion: line 53 pos 12: 'allowReassignment || !_factories.containsKey(T)': Type NavigateService is already registered这个断言是 get_it 的防呆设计:它认为你可能是写错了,所以提醒你这里重复注册了同一个服务,避免覆盖行为掩盖编码失误。断言表达式的语义可以拆解为:
!_factories.containsKey(T):容器中还没有注册过类型T;- 只有"允许重新赋值"或者"从未注册过"两个条件之一成立,注册才被放行。
如果你确实必须覆盖注册(例如热重载、测试场景中替换实现),可以通过设置属性allowReassignment == true来关闭此断言:
GetIt getIt = new GetIt(allowReassignment: true);这样重复注册同一类型时,后注册的实现会覆盖先注册的实现,而不会抛出断言。
05. 获取服务的性能分析
5.1 底层数据结构:一张 Map
我们可以从 get_it 的源码中看到,这个 ServiceLocator 本质上就是用一个 Map 在储存数据:
final _factories = new Map<Type, _ServiceFactory<dynamic>>();这个 Map 的语义非常清晰:
- Key是
Type(即注册时指定的泛型类型,如BusinessService); - Value是
_ServiceFactory<dynamic>(内部包装的服务工厂,记录创建函数、是否单例、是否已初始化等元信息)。
5.2 O(1) 的获取复杂度
因为容器底层是 Map 结构,按 Type 键直接索引取值,所以获取服务的性能是 O(1)——与容器中已注册服务的数量无关。
这一点对"全局注册了几十个甚至上百个服务"的大型 App 而言非常重要:无论注册表多大,getIt<T>()都是一次近乎常数的哈希查找,不会随服务数量增长而出现性能劣化。这也解释了为什么 get_it 敢以 99 行的极简实现被广泛用于 App 全局依赖管理——它的注册与获取路径没有任何多余的开销。
06. 在 YCBlogs 项目中的实践视角
get_it 并不孤立存在,它与仓库中其他技术笔记形成完整的知识闭环:
- 设计思想层面:理解 ServiceLocator 之前,建议先读 依赖倒置原则介绍,掌握 IoC(控制反转)、DI(依赖注入)、DIP(依赖反转原则)三者的区别与联系——get_it 正是这套思想在 Flutter 侧的具体实现;
- 为什么替代单例:参考 单例模式不友好,理解传统单例在可测试性、扩展性、依赖可见性上的缺陷,以及容器化方案如何逐一化解;
- 同类框架对照:Android/Kotlin 侧的依赖注入框架 Koin 使用方式见 Koin框架使用介绍,其
single<HelloRepository> { HelloRepositoryImpl() }与by inject()的"模块声明 + 懒注入"模式,与 get_it 的registerSingleton + getIt<T>()在思想上异曲同工,可对比学习; - 状态管理联动:get_it 经常与 Provider 组合使用,先通过
serviceLocator<BusinessPatternService>()获取服务、再在服务内部操作Provider.of<BusinessPattern>(context)刷新状态,完整案例见 Flutter状态管理 的 8.3 节。
07. 小结与速查表
- 本质:get_it 是仅约 99 行代码的轻量级 ServiceLocator,是"控制反转 + 接口依赖"思想的容器化落地;
- 两步使用:先
registerSingleton / registerLazySingleton / registerFactory注册,再getIt<T>()注入; - 三种注册:工厂每次新建、单例立即初始化且唯一、懒加载单例首次使用才初始化且唯一;
- 重复注册:默认调试模式断言拦截,
allowReassignment: true可显式允许覆盖; - 性能:底层为
Map<Type, _ServiceFactory<dynamic>>,获取复杂度 O(1),与注册量无关。
按此流程,你可以在自己的 Flutter 工程中立即落地一套"注册 + 注入"的依赖管理方案,并借助 Mock 替换轻松编写可测试的业务代码。
- 教程
- 技术博客
- 文档
【免费下载链接】YCBlogs
技术博客笔记大汇总,包括Java基础,线程,并发,数据结构;Android技术博客等等;常用设计模式;常见的算法;网络协议知识点;部分flutter笔记;还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!转载请注明出处,谢谢!
相关推荐
深入解析 Vue 异步组件:三种注册方式与源码级实现原理
深入解析 Vue 异步组件:三种注册方式与源码级实现原理 异步组件是 Vue 减少首屏代码体积、实现按需加载的核心机制。本文以 Vue 2 源码为线索,完整剖析
文档教程前端Kratos Nacos Registry 注册中心接入指南:服务注册、发现与源码原理深度解析
Kratos Nacos Registry 注册中心接入指南:服务注册、发现与源码原理深度解析 导读 本文围绕 Kratos 微服务框架的官方 Nacos 注册
后端微服务RPC框架Web框架云原生MTranServer:轻量级离线翻译服务器解决方案深度解析
MTranServer:轻量级离线翻译服务器解决方案深度解析 项目概述 MTranServer是一款开源的迷你翻译服务器解决方案,其核心设计理念是提供 高速、轻
后端模型推理服务人工智能NLP本地部署
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考