建造者模式
文章目录
亦称:生成器模式,builder
产品的构造需要繁杂的参数或者步骤,建造者模式
建议将对象构造代码从产品类中抽取出来,并将这些步骤放在一个名为建造者的独立对象中。
将一个复杂对象的构建过程与对象最终的表示分离,使得同样的构建过程可以创建出不同的表示。
主要用于对象建造过程比较复杂的情况,“表示"可以理解为对象的属性。
介绍
角色组成:
- builder接口
- 声明在所有类型的生成器中通用的产品建造步骤。
- 具体builder
- 提供建造过程的具体实现。如果各个builder生成的产品不属于同一个产品接口,则builder应提供getResult方法返回该生成器创建的产品。
- 产品Product
- 最终生成的产品对象,可以不实现同一个产品接口。
- 主管director
- 按顺序执行建造步骤。如果各个产品实现了相同的产品接口,可以在director中统一获取建造结果(即产品接口的实例),不再在具体builder中获取。
- 客户端client
- 将某个builder对象与director关联,此后director调用builder对象的方法完成后续的建造任务。
Builder的作用是建造产品,但是建造产品的过程太复杂,所以Builder中有多个BuildPartX()用于创建产品的部分元素,通过GetResult()获取最终的对象。
BuildPartX()方法数量较多,不能直接让客户端去调用创建。所以有一个Director,它会利用Builder中的众多BuildPartX()方法,将产品组装起来。 并通过Builder的GetResult()方法获取到组装好的产品返回给客户端,客户端无需知道组装的细节。
当需要创建不同形式的产品时,其中的一些建造步骤可能需要不同的实现。
在这种情况下,可以创建多个不同的生成器,用不同方式实现一组相同的创建步骤。 然后就可以在创建过程中使用这些生成器(例如按顺序调用多个建造步骤)来生成不同类型的对象。
可以将用于创建产品的一系列建造步骤调用抽取成为单独的director类。 director类定义创建步骤的执行顺序, 而builder则提供这些步骤的实现。
对于客户端代码来说,director类完全隐藏了产品建造细节。 客户端只需要将一个builder与director类关联,然后使用director来建造产品, 就能从builder处获得建造结果了。
适用场景
- 构造函数参数众多
- 建造者模式可以分步骤生成对象,允许你仅使用必须的步骤。应用该模式后,再也不需要将几十个参数塞进构造函数里了。
- 希望使用类似的代码创建不同形式的产品
- 如果需要创建的各种形式的产品,它们的制造过程相似且仅有细节上的差异,可使用建造者模式。
实现步骤
- 抽象通用建造步骤,确保它们可以创建所有形式的产品,在builder接口中定义这些步骤
- 为每个形式的产品建造过程创建对应的具体builder类,实现builder接口中的步骤
- 每个具体的builder类应包含获取结果产品的方法。注意,不能在builder接口中声明该方法,因为不同生成器创建的产品可能没有公共接口,除非它们有公共接口。
- 创建director类,调用builder上的建造步骤
- 所有产品都实现同一接口时,客户端可以通过director获取构造结果。否则,客户端应该通过builder获取构造结果。
基本builder接口中定义了所有可能的制造步骤,具体builder将实现这些步骤来制造特定形式的产品。 同时, director类将负责管理制造步骤的执行顺序。
优缺点
优点
- 可以分步创建对象,暂缓创建步骤或递归运行创建步骤。
- 生成不同形式的产品时,可以复用相同的建造代码。
- 单一职责原则。可以将复杂的建造代码从产品的业务逻辑中分离出来。
缺点
- 需要新增多个类,代码整体复杂程度会有所增加。
与其它模式的关系
- 在许多设计工作的初期都会使用
工厂方法模式
(简单,可以更方便地通过子类进行定制),随后演化为使用抽象工厂模式
、原型模式
或生成器
模式 (更灵活但更加复杂)。 - 生成器重点关注如何分步生成复杂对象;
抽象工厂
专门用于生产一系列相关对象。抽象工厂会马上返回产品,生成器则允许在获取产品前执行一些额外构造步骤。 - 可以在创建复杂
组合模式
树时使用生成器, 因为这可使其构造步骤以递归的方式运行。 - 可以结合使用生成器和
桥接模式
:主管类负责抽象工作,各种不同的生成器负责实现工作。 抽象工厂
、生成器
和原型
都可以用单例模式
来实现。
例子
假设要创建一个资源池,需要设置资源名称(name)、最大总资源数量(maxTotal)、最大空闲资源数量(maxIdle)、最小空闲资源数量(minIdle)等。 其中name必填,maxTotal、maxIdle、minIdle非必填,但是填了一个其它两个也需要填,而且数据值有限制,如不能等于0,数据大小有限制,如maxIdle不能大于maxTotal。
有这样几个方案:
- 使用一个构造函数,所有判断都在构造函数里做。但是一旦输入参数个数增多、校验逻辑复杂,函数就会变得不好维护。需求有变化时,构造函数也要改,不满足
开放封闭原则
。 - 如果对外提供Set方法,因为数据有限制,容易漏掉部分配置。而且有时资源是不可变对象,就不能暴露set方法
- 使用建造者模式。建造者将输入数据准备好,调用产品的创造函数,并返回最终的产品。后面有规则上的变动,只需修改Builder即可。
|
|
|
|
|
|
|
|
|
|
总结
如果一个类中有很多属性,为了避免构造函数的参数列表过长,影响代码的可读性和易用性,我们可以通过构造函数配合set()方法来解决。
但是,如果存在下面情况中的任意一种,就要考虑使用建造者模式了。
- 把类的必填属性放在构造方法中,强制创建对象的时候就设置
- 类的属性之间有一定的依赖关系或者约束条件,需要做复杂的逻辑校验
- 希望创建的对象不可变