意图
构造者模式(建造者模式、生成器模式)是一种创建型设计模式, 使你能够分步骤创建复杂对象。 该模式允许你使用相同的创建代码生成不同类型和形式的对象。
问题
假设有这样一个复杂对象, 在对其进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作。 这些初始化代码通常深藏于一个包含众多参数且让人基本看不懂的构造函数中; 甚至还有更糟糕的情况, 那就是这些代码散落在客户端代码的多个位置。
例如, 我们来思考如何创建一个 房屋House对象。 建造一栋简单的房屋, 首先你需要建造四面墙和地板, 安装房门和一套窗户, 然后再建造一个屋顶。 但是如果你想要一栋更宽敞更明亮的房屋, 还要有院子和其他设施 (例如暖气、 排水和供电设备), 那又该怎么办呢?
1)扩展房屋基类, 然后创建一系列涵盖所有参数组合的子类。 但最终你将面对相当数量的子类。 任何新增的参数 (例如门廊类型) 都会让这个层次结构更加复杂。
2)无需生成子类。 你可以在房屋基类中创建一个包括所有可能参数的超级构造函数, 并用它来控制房屋对象。 这种方法确实可以避免生成子类, 但它却会造成另外一个问题—>拥有大量输入参数的构造函数也有缺陷: 这些参数也不是每次都要全部用上的。对于构造函数的调用十分不简洁。
解决方案
建造者模式结构图:
抽象构造者:构造者模式建议将对象构造代码从产品类中抽取出来, 并将其放在一个名为生成器的独立对象中。
具体构造者:当你需要创建不同形式的产品时, 其中的一些构造步骤可能需要不同的实现。 例如,第一个构造者使用木头和玻璃制造房屋, 第二个构造者使用石头和钢铁, 而第三个构造者使用黄金和钻石。 在调用同一组步骤后, 第一个建造者会给你一栋普通房屋, 第二个会给你一座小城堡, 而第三个则会给你一座宫殿。
指挥者(主管):将用于创建产品的一系列构造者步骤调用抽取成为单独的主管类。 主管类可定义创建步骤的执行顺序, 而构造者则提供这些步骤的实现。
严格来说, 你的程序中并不一定需要主管类。 客户端代码可直接以特定顺序调用创建步骤。 不过, 主管类中非常适合放入各种例行构造流程, 以便在程序中反复使用。
此外, 对于客户端代码来说, 主管类完全隐藏了产品构造细节。 客户端只需要将一个构造者与主管类关联, 然后使用主管类来构造产品, 就能从构造者处获得构造结果了。
产品:房屋(普通房屋,小城堡,宫殿)
代码演示(汽车)
产品对象:
// 产品对象
public class Car {
// 轮胎
private String tire;
// 座椅
private String seat;
// 发动机
private String engine;
public String getTire() {
return tire;
}
public void setTire(String tire) {
this.tire = tire;
}
public String getSeat() {
return seat;
}
public void setSeat(String seat) {
this.seat = seat;
}
public String getEngine() {
return engine;
}
public void setEngine(String engine) {
this.engine = engine;
}
}
具体构造者(大众汽车):
// 大众汽车
public class DaZhongCarBuilder extends CarBuilder {
@Override
public void buildTire() {
car.setTire("大众轮胎");
}
@Override
public void buildSeat() {
car.setSeat("大众座椅");
}
@Override
public void buildEngine() {
car.setEngine("大众发动机");
}
}
具体构造者(丰田汽车):
// 丰田汽车
public class FenTianCarBuilder extends CarBuilder {
@Override
public void buildTire() {
getCar().setTire("丰田轮胎");
}
@Override
public void buildSeat() {
getCar().setSeat("丰田座椅");
}
@Override
public void buildEngine() {
getCar().setEngine("丰田发动机");
}
}
指挥者:
public class CarDirector {
private CarBuilder carBuilder;
// 方式一:通过构造函数设置实际的构造者
// 传入类型是基类,面向抽象编程,符合里氏替换原则
public CarDirector(CarBuilder carBuilder) {
this.carBuilder = carBuilder;
}
// 方式二:通过setter方法设置实际的构造者
public void setCarBuilder(CarBuilder carBuilder) {
this.carBuilder = carBuilder;
}
// 构建复杂产品对象
public Car construct() {
// 指挥者可以决定产品部件的构建顺序
carBuilder.buildTire();
carBuilder.buildSeat();
carBuilder.buildEngine();
return carBuilder.getCar();
}
}
客户端调用:
/**
* 客户端
*/
public class Client {
public static void main(String[] args) {
// 创建一个实际车的构造者
CarBuilder carBuilder = new FenTianCarBuilder();
// 创建指挥者
CarDirector carDirector = new CarDirector(carBuilder);
// 构建出完整产品
Car product = carDirector.construct();
System.out.println(product.getEngine() + "=====" + product.getSeat() + "======" + product.getTire());
}
}
具体的使用场景
使用生成器模式可避免 “重叠构造函数 (telescopic constructor)” 的出现。
(假设你的构造函数中有十个可选参数, 那么调用该函数会非常不方便; 因此, 你需要重载这个构造函数, 新建几个只有较少参数的简化版。 但这些构造函数仍需调用主构造函数, 传递一些默认数值来替代省略掉的参数。构造者模式让你可以分步骤生成对象, 而且允许你仅使用必须的步骤。 应用该模式后, 你再也不需要将几十个参数塞进构造函数里了。)
当你希望使用代码创建不同形式的产品 (例如石头或木头房屋) 时,可使用生成器模式。
(如果你需要创建的各种形式的产品, 它们的制造过程相似且仅有细节上的差异, 此时可使用生成器模式。基本生成器接口中定义了所有可能的制造步骤, 具体生成器将实现这些步骤来制造特定形式的产品。 同时, 主管类将负责管理制造步骤的顺序。)
优缺点:
优点:
你可以分步创建对象, 暂缓创建步骤或递归运行创建步骤。
生成不同形式的产品时, 你可以复用相同的制造代码,加新的具体建造者不会影响现有的类库代码,符合开闭原则。
你可以将复杂构造代码从产品的业务逻辑中分离出来,将产品本身和产品的创建过程解耦,单一职责原因。
缺点:
由于该模式需要新增多个类, 因此代码整体复杂程度会有所增加。
与工厂模式的关系:
抽象工厂模式返回的是一系列相关的产品,而建造者模式返回的是一个具体的完整的复杂对象。
抽象工厂模式的客户端是通过选择具体工厂来生成所需要的对象,而构造者模式是通过Director类去指导如何一步步的生成部件,返回复杂对象。
简单的理解:抽象工厂模式就是汽车配件生产厂,生产不同类型的汽车配件,而建造者模式就是一个汽车组装厂,组装配件返回完整汽车。
扩展:
在Java中的使用:
生成器在 Java 核心程序库中得到了广泛的应用:
java.lang.StringBuilder#append() ( 非同步 )
java.lang.StringBuffer#append() ( 同步 )
java.nio.ByteBuffer#put() (还有 CharBuffer、 ShortBuffer、 IntBuffer、 LongBuffer、 FloatBuffer 和 DoubleBuffer)
javax.swing.GroupLayout.Group#addComponent()
java.lang.Appendable的所有实现
识别方法: 生成器模式可以通过类来识别, 它拥有一个构建方法和多个配置结果对象的方法。 生成器方法通常支持方法链 (例如 someBuilder->setValueA(1)->setValueB(2)->create() )。
java.lang.StringBuilder 中的建造者模式:
Appendable 接口定义了多个 append 方法(抽象方法), 即 Appendable 为抽象建造者, 定义了抽象方法
AbstractStringBuilder 实现了 Appendable 接口方法,这里的 AbstractStringBuilder 已经是建造者,只是不能实例化
StringBuilder 即充当了指挥者角色,同时充当了具体的建造者,建造方法的实现是由 AbstractStringBuilder 完成 , 而 StringBuilder 继承了 AbstractStringBuilder