从代码设计看 Glide 之 Factory Or Builder?
还记得我们在生命周期(中)篇章里,讲到的 Factory (工厂)设计模式吗?
从代码设计看 Glide 系列其他文章:从代码设计看 Glide 系列
引言
Glide 中,有 15 个 Factory 相关的接口,用到这些接口实现去创建对象的地方更是不计其数,那么你有考虑过为什么要使用 Factory 模式吗?
今天,我们就借用 Glide 来讲一讲 Factory (工厂)设计模式
正文
在正式进入文章前,我们先来一个小故事
小故事
👨🏫 二战时期,美国是当时世界上工业水平最高的国家,综合国力强盛,后勤保障突出。当时美军单兵作战口粮质量是非常高的,除了斯帕姆午餐肉,可口可乐也是刚需。几乎所有的美军士兵每周都能领到至少5瓶可乐。
根据美国国防部物资中心数据,二战时期的美军平均一天要喝掉290多万瓶可乐,一年下来多达10亿瓶。
那么,这么高的供应量,美军是如何解决可口可乐的运输问题呢?
答案就是,可口可乐跟着美军建厂,美国打到哪,厂子就开到哪。这样就可以不需要考虑庞大的运输开支了。😅
美军的这种带着工厂到处跑的行为,其实也暗含了工厂模式的其中一个优点。你来猜猜是啥?
Factory 的三个优点
屏蔽创建细节
Factory 模式的第一个优点就是屏蔽创建的细节。我们来看一个例子,还记得生命周期(中)的 RequestManagerRetriever 和 RequestManager 的关系吗?
从 RequestManager 的构造函数中我们可以看出
1 | public RequestManager( |
RequestManager 的创建需要依赖 Glide、Lifecycle、RequestManagerTreeNode、Context 这些类,但是对维护 RequestManager 的 RequestManagerRetriever 来说,这4 个类除了被用于创建 RequestManager 外,是没有其他用途的。如果我们为了这个目的,在 RequestManagerRetriever 的构造函数中加入这 4 个类,势必让 RequestManagerRetriever 的依赖变得更加复杂。增加了不必要的类的关联。
因此,这里我们使用 Factory 模式,将 RequestManagerRetriever 对 RequestManager 的创建细节屏蔽,将 RequestManagerRetriever 对 4 个类的关联转为对 Factory 的关联。
这样做的话,从语义上来说,增强 Request 管理能力, 只会依赖创建 RequestManager 这一结果本身。职责就变的非常清晰。
有些人可能会说,我直接用 new 的方式创建不就好了?完全不需要带着 Factory?
这就要讲到 Factory 的第二个优点了。请往下看
自定义对象
Factory 模式的第二个好处就是可以针对创建的对象提供非常灵活的自定义能力。
同样用上边的例子,我们可以继承 RequestManagerFactory 实现自己的 Factory,来定制创建出自己的 RequestManager。
1 | public interface RequestManagerFactory { |
比如,当我们需要继承 RequestManager 类,来覆写其中的一些方法以增强 Request 管理能力时,如果是通过 Factory 的方式,我们就不需要改动 RequestManagerRetriever 类了。仅仅构建一个新的工厂,交给 RequestManagerRetriever 就可以了。
当然了,虽然 Glide 的定制化类 GlideBuilder 中有配置 RequestManagerFactory 的方法,不过可能因为稳定性的缘故,没有对外开放。
1 | // com.bumptech.glide.GlideBuilder#setRequestManagerFactory |
这里我们解答一下上边的问题,如果这里使用了 new 的方式创建对象,RequestManagerRetriever 就会失去了对 RequestManager 的自定义创建能力,如果后续需要改动 RequestManager,那么只能修改 RequestManagerRetriever 的代码,不符合设计原则中的开闭原则。
有些人可能还有疑问,将 Factory 加入到每个方法的参数中不行吗?这样类的依赖更加弱了(从关联变为依赖)
我猜这里可能有两点考量吧:
- 确保各个方法中创建的 RequestManager 是有统一行为的。
- 当你每个方法都需要这个参数的时候,证明这个类是强依赖的 Factory 的,那么构建的时候传入会比每个方法都传入更合适
拆分管理和创建职责
一般来说,框架在提供对象管理的同时,也兼任了对象创建。但从职责上来说,其实是可以拆分开的。框架可以只负责管理实体对象的集合以及他们的行为,是没有必要关注他们是怎么创建出来的。
比如这里的 RequestManagerFactory,以及我们经常用到 Java 线程池 ExecutorService 中的 ThreadFactory,还有 Spring 框架中常见的 bean 的创建等等
💡 以上这三个优点,大家可以用到日常的 Coding 中来判断当前是否需要使用 Factory 模式。
下边我们来讲一讲,Factory 实现过程中的一个细节,如何携带创建对象所需要的参数?
如何优雅的携带参数
携带参数,大致可以分为 3 类:
- build 方法无参,Factory 构造方法无参
- build 方法有参,Factory 构造方法无参
- build 方法有参,Factory 构造方法有参
之所以提到这个问题,是因为 Factory 初始化的方式一般为使用者决定,而 build 函数调用的方式一般由框架决定。
这两个位置获取参数的难度可是不一样的。
- 如果在 Factory 的构造函数中携带参数
Factory 是客户端去初始化的,如果客户端可以获取到这些参数,或者构建出这些参数,那么 Factory 构造函数带着完全没问题的。甚至构造函数也不需要带,在 Factory 内部生成就行。
- 如果在 build 函数中携带参数
如果是 build 函数带的话,build 函数调用的地方一般是框架内部,就需要框架内部来解决参数的问题了。如果参数比较好获取,那么用到的时候传给 build 函数即可,否则就需要框架内部的某个对象来存储这些参数,这样也会额外带来一些复杂度的问题。
- 如果这二者获取参数都没什么难度,推荐在 build 函数中携带参数,
这样对 Factory 来说依赖的内容减少了,可以做最高层级的抽象。可以通过反射动态生成 Factory,甚至可以使用配置的方式生成 Factory,更加灵活。
当然,在哪里携带参数更加优雅,对 Glide 也是一个比较头疼的事情,不信你看
在我们上期讲到的 RequestManager 中有一段代码:
1 | private RequestManager supportFragmentGet(...){ |
因为 Factory 需要 Glide 这个参数,所以我们只能通过 Glide.get() 先把 glide 拿到才行,但这里的 Glide.get() 确实不是很优雅。
对应的提交记录也指出了这个问题。
1 | Start removing lookups of the Glide singleton in the framework code |
和 Builder 的不同点
Builder 和 Factory 都是用来创建对象的。从这个职责上来说,他们是等价的。那他们可以互相替换吗?
如果对以下这 3 点有特殊要求的话,他们是没法互相替换的。
Builder 设计模式 | Factory 设计模式 | |
---|---|---|
创建约束 | 1. Buidler 会将你创建对象用到的全部参数都列举出来,这就意味着你只能在有限的范围内配置创建对象。 2. Builder 不会让你真正的接触到要创建的对象,因为 Builder 自身要把控对象的创建过程。 | 1. Factory 不对创建过程约束。 2. Factory 将对象创建过程完全开放,不关心任何对象创建的细节。 |
API 易用性 | Builder 提供了非常好的 api 易用性。1. 可以通过 Builder 使用优雅简洁的链式调用 2.还能通过 Builder 非常轻松的知道这个框架支持哪些能力 | Factory 模式使用上本身相对简单,因为他的能力更多集中在拓展上。 |
拓展性 | Builder 不可以继承拓展。1. 如果变更参数,需要修改原有 Builder 类的内容。(给 Builder 增加一个新的参数,有多痛苦就不用我多说了吧) | 1. Factory 可以通过拓展的方式来增加参数,并不一定要修改原来的 Factory 工厂。2. 还可以使用继承,组合等其他模式,来增加代码复用能力。 |
哪些场景下适合用 Factory,哪些场景适合用 Builder ?
Factory Or Builder ?
通过上边的对比表格,我们可以看出来:
- Builder 的设计思路其实面向框架的,可以让开发者合理配置出对象,从而正确的使用框架。
- Factory 的设计思路其实是面向开发者的,给了开发者更多的自由拓展空间。从而增强框架的能力。
总结来说就是,Builder 模式更适合用来配置管理,而 Factory 模式更适合用来能力拓展。
示例
Builder 设计模式
- Glide 通过 GlideBuilder 来进行参数配置管理
- Request 通过 RequestBuilder 来配置产生
Factory 设计模式
- 我们后边即将讲到的 Glide 的 Loader 模块
- Java 线程池 ExecutorService 中的 ThreadFactory
当然,Factory 和 Builder 并不是相反的两面,他们也可以协作一起使用
比如 GlideBuilder 和 RequestOptionsFactory 会一起协作
1 | public GlideBuilder setDefaultRequestOptions(final RequestOptions requestOptions) { |
推荐大家带着思考,再去多找几个 Factory 和 Builder 的源码来读。会加深理解。
结束语
这篇文章其实没有特别多的着眼于 Factory 和 Builder 模式是如何实现的,更多的是我自己在使用 Builder 和 Factory 过程中的一些思考,思考他们各自的特性是什么?这些特性能不能让我写的框架或者代码变的更加易用,更加有拓展性。
希望你可以在看完这篇文章后借此触发更多的思考,学会用更高一层的思维以及更加系统的方式去解决问题。
下期,我们继续回到 Glide 解析的主线,一起来看看 Glide 如何通过 Loader 做到支持加载各种数据类型的?
从代码设计看 Glide 之 Factory Or Builder?
http://www.0xforee.top/2023/09/06/factory-or-builder-of-glide-code-design/