建造者模式的两种不同实现方式 | 总字数: 2.8k | 阅读时长: 10分钟 | 浏览量: |
创建一个对象最常用的方法就是通过 new
关键字创建出一个对象,但是有的时候这种创建对象的方法并不适用。
建造者模式又称为构建者模式或生成器模式,属于创建型模式,它用于组装复杂的实例。它可将一个复杂对象的构建与其表示分离 ,使得同样的构建过程可以创建不同的表示。用户只需要指定需要建造的类型就可以 得到该类型对应的产品实例 , 不关心建造过程细节。
建造者模式有两种写法,一种是 GoF 中提出的传统的建造者模式写法,一种是现在常用的写法。
传统的建造者模式 这里介绍的建造者模式的实现接近于 GoF 的思想。
登场角色:
Product(产品):我们需要获得的类。 Builder(抽象建造者):定义用于生成实例的接口(API),准备用于生成实例的方法。 ConcreteBuilder(具体的建造者):负责实现 Builder 角色接口(API)的类。ConcreteBuilder 还定义了获取最终生成结果(产品)的方法。 Director(监工/指挥者):负责使用 Builder 角色的接口(API)来生成实例,它并不依赖于 ConcreteBuilder 角色(它只调用在 Builder 角色中被定义的方法)。注意是具体类。组装的具体过程隐藏在 Director 中。 Client(使用者):使用 Builder 模式。 在 GoF 书中没有 Client 和 Product 这两个角色,只是为了方便理解而引入。
建造者代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 public class Product { private int param1 = DEFAULT_VALUE; private int param2 = DEFAULT_VALUE; private int param3 = DEFAULT_VALUE; } public abstract class Builder { protected Product product = new Product (); public abstract Builder buildPart1 () ; public abstract Builder buildPart2 () ; public abstract Builder buildPart3 () ; public abstract Product getProduct () ; } public static class ConcreteBuilder extends Builder { @Override public Builder buildPart1 () {} @Override public Builder buildPart2 () {} @Override public Builder buildPart3 () {} @Override public Product getProduct () { return product; } } } public class Director { private Builder builder; public Director (Builder builder) { this .builder = builder; } public void build () { builder.buildPart1(); builder.buildPart2(); builder.buildPart3(); } } public class Client { public static void main (String[] args) { Builder builder = new ConcreteBuilder (); Director director = new Director (builder); director.build(); Product product = builder.getProduct(); } }
Builder 模式的时序图:
sequenceDiagram
participant C as Client
participant D as Director
participant B as ConcreteBuilder
activate C
C ->> B : new
activate B
deactivate B
C ->> D : build
activate D
D ->> B : buildPart1
activate B
B -->> D :
deactivate B
D ->> B : buildPart2
activate B
B -->> D :
deactivate B
D ->> B : buildPart3
activate B
B -->> D :
deactivate B
D -->> C :
deactivate D
C ->> B : getProduct
activate B
B -->> C :
deactivate B
deactivate C
在建造者模式中,Main 不知道 Builder 类,Director 不知道 ConcreteBuilder 类。可替换性就体现在「只有不知道子类才能替换」。
对于上面建造者模式写法,不同文章中的实现也有所不同:
有些写法是使用 Director 获取产品。这种方法让 Director 完全控制构建流程,Builder 只负责部件构造,构建的代码部分实现了复用。用户只需和 Director 交互。 有些写法是像上面文章讲的那样通过 ConcreteBuilder 获取产品。这种方式会呈现一定的构造灵活度。 我们可以不必纠结上面这些区别,直接来看最常用的写法。
建造者模式常用写法——省略 Director 角色 建造者模式的常用用法,是一种混合模式。
假设我们需要定义一个资源池配置类 ResourcePoolConfig
。这里的资源池可以简单理解为线程池、连接池、对象池等。包含以下参数:
name
:资源名称 maxTotal
:最大总资源数量 maxIdle
:最大空闲资源数量 minIdle
:最小空闲资源数量 我们的构造函数或许是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class ResourcePoolConfig { private static final int DEFAULT_MAX_TOTAL = 8 ; private static final int DEFAULT_MAX_IDLE = 8 ; private static final int DEFAULT_MIN_IDLE = 0 ; private String name; private int maxTotal = DEFAULT_MAX_TOTAL; private int maxIdle = DEFAULT_MAX_IDLE; private int minIdle = DEFAULT_MIN_IDLE; public ResourcePoolConfig (String name, Integer maxTotal, Integer maxIdle, Integer minIdle) { } }
不管可选或必选配置项,我们都用构造方法配置这些参数时,就叫做折叠构造函数模式(Telescoping Constructor Pattern)。
1 2 3 4 5 6 7 8 public class ResourcePoolConfig { public ResourcePoolConfig (String name) {} public ResourcePoolConfig (String name, Integer maxTotal) {} public ResourcePoolConfig (String name, Integer maxIdle, Integer minIdle) {} public ResourcePoolConfig (String name, Integer maxTotal, Integer maxIdle, Integer minIdle) {} }
我们发现一些问题:
当 ResourcePoolConfig
的必选配置项变为 8 个、10 个时我们的构造函数会越来越长 当出现多个可选配置项时,我们构造函数的重载方法会越来越多。如果一些参数类型几乎都相似,那么参数传递时很容易传混或出错。 对于可选配置项的一个简单的解决方式为使用 setXXX()
函数来给成员变量赋值,以替代冗长繁琐的构造函数。这也是所谓的 Java Bean 模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class ResourcePoolConfig { private static final int DEFAULT_MAX_TOTAL = 8 ; private static final int DEFAULT_MAX_IDLE = 8 ; private static final int DEFAULT_MIN_IDLE = 0 ; private String name; private int maxTotal = DEFAULT_MAX_TOTAL; private int maxIdle = DEFAULT_MAX_IDLE; private int minIdle = DEFAULT_MIN_IDLE; public ResourcePoolConfig (String name) { if (StringUtils.isBlank(name)) { throw new IllegalArgumentException ("name should not be empty." ); } this .name = name; } public void setMaxTotal (int maxTotal) {} public void setMaxIdle (int maxIdle) {} public void setMinIdle (int minIdle) {} }
但使用 setXXX()
函数还存在一些问题:
必填的配置不能可以使用 setXXX()
函数,因为如果各个必填配置存在互相依赖校验,这种校验逻辑无法实现。所以必选项配置的问题还是没有解决。 不能保证参数传递的先后次序。 一旦必填的配置多了起来,构造参数的列表还是会长起来。 如果我们希望 ResourcePoolConfig
是不可变的对象,那么 setXXX()
就会出现问题。 为解决上述问题,Builder 模式诞生。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class Product { private int required_param_1 = DEFAULT_VALUE; private int optional_param_2 = DEFAULT_VALUE; private int optional_param_3 = DEFAULT_VALUE; private Product (Builder builder) { this .required_param_1 = builder.required_param_1; this .optional_param_2 = builder.optional_param_2; this .optional_param_3 = builder.optional_param_3; } public static class Builder { private int required_param_1 = DEFAULT_VALUE; private int optional_param_2 = DEFAULT_VALUE; private int optional_param_3 = DEFAULT_VALUE; public Builder (int required_param_1) { this .required_param_1 = required_param_1; } public Builder setOptionalParam2 (int val) { this .optional_param_2 = val; return this ; } public Builder setOptionalParam3 (int val) { this .optional_param_3 = val; return this ; } public Product build () { return new Product (this ); } } }
使用方式:
1 2 3 4 5 Product product = new Product .Builder(val1) .setOptionalParam2(val2) .setOptionalParam3(val3) .build();
与经典的 Builder 模式相比,这种模式把 Director 角色给省略了,另外在内部静态 Builder 的构建方法中返回了 this
自身,方便流式调用。
Lombok 中的 @Builder
注解 Lombok 是一个常用的 Java 库,通过添加注解可自动实现建造者模式代码或 Getter、Setter 等。其中,@Builder
注解可以用来实现建造者模式,省略手动编写建造者模式的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import lombok.Builder;import lombok.ToString;@Builder @ToString public class Person { private String name; private int age; private String address; private String email; private String phone; }
使用方式:
1 2 3 4 5 6 7 8 Person person = Person.builder() .name("王五" ) .age(28 ) .address("上海市浦东新区" ) .email("[email protected] " ) .phone("13600000000" ) .build(); System.out.println(person);
模式的对比与选用 相关的设计模式:
Composite 模式:有些情况下 Builder 模式生成的实例构成了 Composite 模式。 门面模式 Facade: 在 Builder 模式中,Director 角色通过组合 Builder 角色中的复杂方法向外部提供可以简单生成实例的接口(API)(相当于示例程序中的 construct 方法)。 Facade 模式中的 Facade 角色则是通过组合内部模块向外部提供可以简单调用的接口(API)。 建造者模式与工厂模式的对比 比较:
建造者模式:让建造者类来负责对象的创建工作。 站内文章 工厂模式 :工厂类负责对象的创建工作。创建不同但相关类型的对象(继承同一父类或接口 Product 的一组子类),由给定参数决定创建哪种类型的对象。Factory 抽象类定义的是一个实例产生的具体流程,子类实现具体的处理。 模式选用:
当创造一个对象需要很多步骤时 , 适合使用建造者模式 当创造一个对象只需要一个简单的方法就可以完成,适合使用工厂模式 建造者模式与抽象工厂模式的对比 Builder 模式和 Abstract Factory 模式都用于生成复杂的实例。
Builder 模式则是分阶段地制作复杂实例。 Abstract Factory 模式通过调用抽象产品的接口(API)来组装抽象产品,生成具有复杂结构的实例。抽象工厂模式强调的是产品族的创建,即相关的产品一起被创建出来,而建造者模式强调的是一个复杂对象的创建,即它的各个部分逐步被创建出来。 后记 参考与归档 本文 PlantUML 代码归档 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Client class Director{ builder {method} build } abstract class Builder{ product {method} {abstract} buildPart1 {method} {abstract} buildPart2 {method} {abstract} buildPart3 {method} {abstract} getProduct } class ConcreteBuilder{ {method} buildPart1 {method} buildPart2 {method} buildPart3 {method} getProduct } class Product{} Client -> Director : Uses Director o- Builder Builder <|-- ConcreteBuilder Client --> ConcreteBuilder : Uses Builder *- Product
参考文章