建造者模式又被成为生成器模式,是一种使用频率比较低,相对复杂的创建型模式,在很多源码框架中可以看到建造者的使用场景,稍后我们会在本文末尾展示几个框架的使用案例。
建造者模式所构造的对象通常是比较复杂而且庞大的,也是按照既定的构建顺序将对象中的各个属性组装起来。与工厂模式不同的是,建造者模式主要目的是把繁琐的构建过程从产品类和工厂类中抽离出来,进一步解耦,最终实现用一套标准的制造工序制造出不同的产品。
1. 定义
建造者模式 的官方定义是:将一个复杂对象的构建和表示分离,使得同样的构建过程可以创建不同的表示。建造者模式一步一步地创建一个复杂的对象,它允许用户通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的构造细节。
官方的表达还是挺难理解的,总体意思就是,构建过程固定,构建使用的每个组件都可以通过继承或者实现 呈现出多态的特性,这依赖于抽象建造者接口和具体的建造者实现类两个组件来表示。
在建造者模式中,通常包含4个角色:
- 产品:复杂的产品类,建造者的最终产物,构建过程相对复杂,需要很多组件组装而成。
- 抽象建造者:建造者接口,包含两个方便,一个是构成产品的各个属性,一个是各个属性的构建步骤方法。
- 建造者实现:建造者接口的实现类,可以有多个,对应不同的组件实现细节,但是不会包含构建的顺序逻辑。
- 指导者:客户端依赖的创建产品的指导者类,指导者类会通过建造者接口引入具体的建造者实现,并且包含了具体的构建顺序。
2. 代码实现
建造者的实现逻辑想对复杂,下面展示一个酒水制造的代码案例,某个酒厂生产啤酒和红酒两类产品,对应上述的四个角色,代码实现如下:
- 1. 产品
// 产品对象
public class Wine {
// 用list封装制作工艺,表示制作的顺序是固定的
private List<String> list = new ArrayList();
// 1.准备原材料
public void setPrepareMaterial(String prepareMaterial) {
list.add(prepareMaterial);
}
// 2.制作工艺
public void setCraftsmanShip(String craftsmanShip) {
list.add(craftsmanShip);
}
// 3.出厂包装
public void setFactoryPackage(String factoryPackage) {
list.add(factoryPackage);
}
@Override
public String toString() {
return "Wine{" + "list=" + list + '}';
}
}
- 2. 抽象建造者
// 抽象的建造者
public abstract class AbstractBuilder {
// 自定义产品
Wine wine = new Wine();
// 设置原材料
public abstract void setPrepareMaterial();
// 设置制作工艺
public abstract void setCraftsmanShip();
// 设置出厂包装
public abstract void setFactoryPackage();
// 获取产品 - 注意这里是个钩子方法,子类可以不实现
public Wine getProduct() {
return wine;
}
}
- 3. 建造者实现
// 啤酒建造者-具体的子类实现
public class BeerBuilder extends AbstractBuilder {
@Override
public void setPrepareMaterial() {
wine.setPrepareMaterial("1.准备原材料:小麦 + 豆子");
}
@Override
public void setCraftsmanShip() {
wine.setCraftsmanShip("2.制作工艺:发酵 + 蒸馏");
}
@Override
public void setFactoryPackage() {
wine.setFactoryPackage("3.出厂包装:易拉罐 + 纸箱");
}
}
// 葡萄酒制造者 - 具体的子类实现
public class GrapeBuilder extends AbstractBuilder{
@Override
public void setPrepareMaterial() {
wine.setPrepareMaterial("1.准备原材料:葡萄 + 酵母");
}
@Override
public void setCraftsmanShip() {
wine.setCraftsmanShip("2.制作工艺:发酵 + 地下贮存");
}
@Override
public void setFactoryPackage() {
wine.setFactoryPackage("3.出厂包装:玻璃瓶 + 木箱");
}
}
- 4. 指导者
// 指挥者 - 构造产品对象
public class Director {
private AbstractBuilder abstractBuilder;
public Director(AbstractBuilder abstractBuilder) {
this.abstractBuilder = abstractBuilder;
}
// 这里固定制造的工序,按照固定步骤执行
public Wine createProduct() {
// 第一步:设置原材料
abstractBuilder.setPrepareMaterial();
// 第二步:设置制作工艺
abstractBuilder.setCraftsmanShip();
// 第三步:设置出厂包装
abstractBuilder.setFactoryPackage();
// 第四步,返回产品
return abstractBuilder.getProduct();
}
}
- 客户端
// 客户端
public class Client {
public static void main(String[] args) {
// 啤酒制造
Director beerDirector = new Director(new BeerBuilder());
Wine beer = beerDirector.createProduct();
System.out.println(beer);
// 红酒制造
Director grapeDirector = new Director(new GrapeBuilder());
Wine grape = grapeDirector.createProduct();
System.out.println(grape);
}
}
3. UML类图
对照上述的代码Demo,我们来把这个案例的类图画出来,对应如下:
4. 简化
因为建造者是属于相对复杂的一种模式,在实际的应用当中有很多种简化的写法,比如可以忽略指导者类、建造者接口等等。下面是一种实现方式,这种实现方式在很多源码中可以看到具体实现。
通常在产品类的构造函数参数超过4个,而且这些参数中有一些是必填项,考虑使用这种建造者模式。现在我们来构建一个电脑的产品对象。
// 电脑产品对象
public class Computer {
private String cpu; // 必选
private String ram; // 必选
private String keyboard; // 可选
private String mouse; // 可选
private String display; // 可选
}
这种bean类的属性设置有两种方式,一个是构造函数入参,一个是通过set()方法入参,这两种方式都有些问题。构造函数入参,当参数较多的时候,类型相同的情况下,属性的顺序容易混乱;第二个中set()方式,一个对象会支持在很多模块中设置,因为类中的属性是可以分布设置的,所以容易出现属性状态变化造成错误。
下面是一种简易的建造者实现方式(在实际场景中可能属性的构建过程很复杂):
- 建造者模式
// 电脑产品对象
public class Computer {
private final String cpu; // 必选
private final String ram; // 必选
private final String keyboard; // 可选
private final String mouse; // 可选
private final String display; // 可选
private Computer(Computer.Builder builder) {
this.cpu = builder.cpu;
this.ram = builder.ram;
this.keyboard = builder.keyboard;
this.mouse = builder.mouse;
this.display = builder.display;
}
public static Computer.Builder builder(String cpu, String ram) {
return new Computer.Builder(cpu, ram);
}
public static class Builder {
private String cpu; // 必选
private String ram; // 必选
private String keyboard; // 可选
private String mouse; // 可选
private String display; // 可选
Builder(String cpu, String ram) {
this.cpu = cpu;
this.ram = ram;
}
public Builder cpu(String cpu) {
this.cpu = cpu;
return this;
}
public Builder ram(String ram) {
this.ram = ram;
return this;
}
public Builder keyboard(String keyboard) {
this.keyboard = keyboard;
return this;
}
public Builder mouse(String mouse) {
this.mouse = mouse;
return this;
}
public Builder display(String display) {
this.display = display;
return this;
}
public Computer build() {
return new Computer(this);
}
}
@Override
public String toString() {
return "Computer{" + "cpu='" + cpu + '\'' + ", ram='" + ram + '\'' + ", keyboard='" + keyboard + '\''
+ ", mouse='" + mouse + '\'' + ", display='" + display + '\'' + '}';
}
}
- 客户端实现
// 客户端
public class Client {
public static void main(String[] args) {
Computer computer = Computer.builder("cpu", "ram")
.cpu("cpu-1").mouse("mouse").display("display").build();
System.out.println(computer);
}
}
5. 总结
建造者模式的核心在于如何一步一步地构建一个包含多个组成部件的完整对象,使用相同的构建过程可以构建不同的产品。在软件开发中,如果需要创建复杂对象,并且系统系统具备良好的灵活性和扩展性,可以考虑使用建造者模式。
- 首先建造者模式使得客户端和产品创建的过程解耦,客户端可以不知道产品创建的内部细节;
- 构建过程固定的情况下,构建的细节可以多变性,这就可以很方便的增加不同的构造实现,符合开闭原则;
- 将每个属性的构建过程分解在不同的方法中,精细化管理,高内聚才能低耦合,符合单一职责。
6. 框架中的应用
- StringBuilder和StringBuffer
- SqlSessionBuilder
- Lombok的@Builder注解