论 Provider 与单例模式

515 阅读2分钟

在 flutter 中提供某个功能可以用单例模式,也可以用 Provider 之类的依赖注入库。本文在单例和 provider 模式间做了一些对比。本文的中心思想同样适用于其他技术栈。

介绍

Provider 是 flutter 的一种提供依赖注入功能的库。它与 vue 的 pinia 或者 go 的 context.Context 类似。都可以从一个全局唯一的地方获取之前创建过的对象。

如果把 flutter 中的随处可见的 BuildContext 对象比喻为一个 map 的话,那么可以把创建 Provider 理解为 map.set。同样也可以把 Provider.of(context) 或者 context.read 理解为 map.get。

provider 是一种特殊的“单例模式”

假设现在在 app 最外层注入了一个用于提供网络服务的 NetProvider 的话。那么在 app 的任何地方都能通过 context.read 获取当时 provide 的那个 NetProvider。

这可以理解为给一个全局的 map 变量 set 了一个值。然后在 widget 的任何地方都能通过 map.get 获取当时 provide 的那个实例。

那么现在有几个问题:

Q: 在整个 App 的生命周期中 NetProvider 一共被创建了几次?

A: 1 次。

Q: 在整个 App 的生命周期中多次运行 context.read 得到的是不是同一个 NetProvider 实例?

A: 是的。

假设现在 app 是用一个叫 HttpDio 的单例类提供网络功能的话。单例类的实例会在首次执行 HttpDio 构造方法的时候创建。之后调用 HttpDio 的构造方法只会返回相同的对象。

那么现在有几个问题:

Q: 在整个 App 的生命周期中 HttpDio 实例一共被创建了几次?

A: 1 次。因为只有单例类内部的 _instance 变量才是真正地被创建的。

Q: 在整个 App 的生命周期中多次运行 HttpDio() 得到的是不是同一个 NetProvider 实例?

A: 是的。

既然用 map 的方式与用单例模式的方式表现一致的话。那么可不可以把这种 “map 模式” 理解为一种特殊的“单例模式”呢?

“Provider 模式” 更符合直觉

现在我们把所有代码全部“语义化”一下。

一个普通构造函数对应的是 create 语义。那么 “单例模式的构造函数” 就对应 create_or_get 语义。应用中的逻辑可以抽象为由以下命令构成的序列。

a = create_or_get()
a = create_or_get()
a = create_or_get()
...

map.set 对应的是 create 语义。因为确实创建了对象嘛。只是创建到了 map 里面罢了。map.get 对应的是 get 语义。这没什么问题。那么应用中的逻辑就可以抽象为由以下命令构成的序列。

create(b)
b = get()
b = get()
b = get()
...

难道不觉得后一种方法更加符合直觉吗?