这篇文章来讲解比较常用的创建型设计模式-建造者模式。建造者模式主要用来建造复杂的对象。
本文UML类图链接为:https://www.processon.com/view/link/6080def6079129456d4beecf
本文代码链接为:https://github.com/shidawuhen/asap/blob/master/controller/design/9builder.go
1.定义
1.1建造者模式
建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
UML类图:
1.2分析
要理解定义,得先理解定义中的“表示”。“表示”可以简单的认为是类中成员变量值不同。例如要展示一个长方形,需要左上角和右下角,如果这些值不一样,“表示”也就不一样。
Builder的作用就是建造Product,建造Product太过复杂,所以Builder中有多个BuildPart()用于组装Product的部分元素,通过GetResult()获取建造好的Product对象。
又因为Builder中的BuildPart()方法太多,如果直接给客户端使用的话,调用方做组装成本会比较高,所以有Director,它会利用Builder中众多BuildPart()方法,将Product组合起来。然后客户端就能够通过Builder的GetResult()方便的获取到组装好的Product,而无需知道组装的细节。
2.使用场景
对于建造者的使用场景,《大话设计模式》和《设计模式之美》中讲的都不是特别理想。《大话设计模式》使用的是造胖小人、瘦小人的例子,属于为了讲解而强出的一个例子,好处是完全符合UML类图。《设计模式之美》里使用构建复杂对象的例子,好处是例子确实很常见,内核也是建造者模式的内核,但是实现上并不很符合UML类图。思考再三,还是选择实际一点的例子吧,毕竟学习设计模式就是为了具体使用的。
假设我们要创建一个资源池,需要设置资源名称(name)、最大总资源数量(maxTotal)、最大空闲资源数量(maxIdle)、最小空闲资源数量(minIdle)等。其中name必填,maxTotal、maxIdle、minIdle非必填,但是填了一个其它两个也需要填,而且数据值有限制,如不能等于0,数据间有限制,如maxIdle不能大于maxTotal。
碰到这种问题如何处理呢?
我们可以使用构造函数,所有的判断都在构造函数里做。但是一旦输入参数很多,会导致调用的时候容易写乱,而且构造函数里判断太多,后面需求有变化,构造函数也需要更改,不满足开放封闭原则。
如果使用set,因为数据间有限制,很容易漏掉部分配置。而且有时资源对象为不可变对象,就不能暴露set方法。
这个时候,建造者模式就能发挥作用了。建造者将输入数据整理好,将数据以对象的方式传递给资源类的构造函数,资源类拿到数据直接获取数值即可,是不是就达到了分离的效果。今后有规则上的变动,只需要修改Builder即可。
3.代码实现
1 | package main |
输出:
➜ myproject go run main.go
RedisResourceBuilder setName redis
RedisResourceBuilder setMinIdle 10
RedisResourceBuilder setMaxIdle 10
RedisResourceBuilder setMaxTotal 20
RedisResourceBuilder build
Product的数据为 {name:redis maxTotal:20 maxIdle:10 minIdle:10}
这段代码通过RedisResourceBuilder将Product的参数做了检查,使Product只需要关注自身的核心逻辑。Director负责组装,使调用方无需知道建造的细节。
如果需要更改为MySQL的资源,只需要创建MySQLResourceBuilder,然后实现接口函数即可。不过这种情况很少见。做的项目碰到的例子中,用在参数检验上相对多一些。
总结
如果一个类中有很多属性,为了避免构造函数的参数列表过长,影响代码的可读性和易用性,我们可以通过构造函数配合 set() 方法来解决。但是,如果存在下面情况中的任意一种,我们就要考虑使用建造者模式了。
- 把类的必填属性放到构造函数中,强制创建对象的时候就设置
- 类的属性之间有一定的依赖关系或者约束条件
- 希望创建不可变对象