建造者模式
建造者模式 (Builder Pattern) 是一种创建型设计模式,旨在将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。
核心思想:
想象一下你要组装一台复杂的电脑。这台电脑有很多部件(CPU、内存、硬盘、显卡、主板、电源、机箱等),而且这些部件的组合方式和选择可能会有很多种,导致最终的电脑配置各不相同。
如果直接在客户端代码中一步步设置这些部件,代码会变得非常冗长、复杂且容易出错。而且,如果需要创建不同配置的电脑,就需要重复很多相似的设置代码。
建造者模式通过引入以下角色来解决这个问题:
-
产品 (Product):要构建的复杂对象。例如,一台电脑。
-
抽象建造者 (Builder):定义了创建产品各个部件的抽象接口,以及一个返回最终产品的方法。它不知道具体部件的细节。例如,一个 ComputerBuilder 接口,包含 buildCPU(), buildRAM(), buildHardDisk(), getResult() 等方法。
-
具体建造者 (ConcreteBuilder):实现了抽象建造者接口,负责具体部件的创建和组装。它知道如何创建和装配特定类型的产品部件。例如,GamingComputerBuilder (游戏电脑建造者) 和 OfficeComputerBuilder (办公电脑建造者)。
-
指挥者 (Director) (可选,但常见):负责按照特定的顺序或算法来调用具体建造者的方法,以构建产品。指挥者不关心具体部件的实现细节,只关心构建的步骤。例如,一个 ComputerDirector 类,它有一个 construct(ComputerBuilder builder) 方法,在该方法内部按顺序调用 builder.buildCPU(), builder.buildRAM() 等。
模式结构图:
+----------------+ uses +---------------------+
| Client |--------------->| Director |
+----------------+ +---------------------+
| | uses
| creates |
v v
+---------------------+ constructs +---------------------+
| ConcreteBuilderA |<-----------------------| Builder |
+---------------------+ (implements) +---------------------+
| ^
| builds | (realizes)
v |
+----------------+ +---------------------+
| ProductA | | ConcreteBuilderB |
+----------------+ +---------------------+
| builds
v
+----------------+
| ProductB |
+----------------+
工作流程:
-
客户端创建一个具体建造者对象 (e.g., GamingComputerBuilder)。
-
客户端将这个具体建造者对象传递给指挥者对象 (e.g., ComputerDirector) (如果使用了指挥者)。
-
指挥者调用具体建造者的部件构建方法,按照预定的顺序或算法进行构建。
-
(或者,如果不使用指挥者,客户端可以直接调用具体建造者的方法来逐步构建对象。)
-
当产品构建完成后,客户端从具体建造者那里获取构建好的产品实例 (通过 getResult() 或类似方法)。
优点:
-
封装性好,构建和表示分离:
-
客户端不需要知道产品内部组成的细节。
-
产品的构建过程被封装在指挥者和具体建造者中。
-
可以改变产品的内部表示,而无需修改客户端或指挥者的代码(只要抽象建造者接口不变)。
-
-
更好的控制构建过程:指挥者可以精确控制复杂对象的构建步骤和顺序。
-
易于扩展:可以很容易地添加新的具体建造者,以创建不同表示的产品,而无需修改现有代码(符合开闭原则)。
-
代码更清晰,可读性高:将复杂的构建逻辑从客户端代码中分离出来,使得客户端代码更简洁。对于有很多可选参数或配置的对象,使用链式调用的建造者模式(下面会提到)可以使对象的创建过程非常易读。
-
可以创建不同表示的产品:使用相同的构建过程(指挥者),但传入不同的具体建造者,可以得到不同类型或配置的产品。
缺点:
-
增加了类的数量:需要为每个产品创建至少一个具体建造者类,如果产品种类繁多,会导致系统中类的数量增加。
-
产品必须有共同点:建造者模式适用于产品内部结构相似,但具体部件或配置不同的情况。如果产品之间的差异过大,则不适合使用建造者模式。
-
构建过程相对固定:如果产品的构建步骤差异很大,可能需要多个指挥者或更复杂的逻辑。
适用场景:
-
当创建一个对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
-
当构建过程需要允许被构建的对象有不同的表示时。
-
当需要创建一个复杂对象,并且该对象的构建步骤稳定,但具体部件或属性可以灵活变化时。
-
当对象的构造函数参数过多,或者有很多可选参数,导致构造函数难以使用时(此时可以使用链式调用的建造者模式变体)。
建造者模式的常见变体:链式调用 (Fluent Interface / Chaining)
这是一种非常流行的建造者模式实现方式,尤其在 Java 中,例如 StringBuilder、Lombok 的 @Builder 注解,以及很多第三方库。
在这种变体中:
-
通常没有明确的指挥者类。
-
具体建造者的方法(如 setCPU(), setRAM())在设置完部件后返回建造者自身 (return this;)。
-
最终有一个 build() 方法来创建并返回产品对象。
示例 (Java - 链式调用变体):
假设我们要创建一个 Person 对象,它有很多可选属性。
产品类 (Product):
class Person {
private final String firstName; // 必选
private final String lastName; // 必选
private final int age; // 可选
private final String phone; // 可选
private final String address; // 可选
private Person(Builder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}
@Override
public String toString() {
return "Person{" +
"firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", age=" + age +
", phone='" + phone + '\'' +
", address='" + address + '\'' +
'}';
}
// 静态内部类 Builder
public static class Builder {
private final String firstName; // 必选
private final String lastName; // 必选
private int age; // 可选,有默认值
private String phone = ""; // 可选
private String address = ""; // 可选
// 必选参数通过构造函数传入
public Builder(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public Builder age(int age) {
this.age = age;
return this; // 返回自身,实现链式调用
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
// build() 方法创建并返回 Person 对象
public Person build() {
// 可以在这里进行一些校验
if (firstName == null || lastName == null) {
throw new IllegalArgumentException("First name and last name cannot be null");
}
return new Person(this);
}
}
}
客户端代码:
public class BuilderDemo {
public static void main(String[] args) {
// 使用链式调用创建 Person 对象
Person person1 = new Person.Builder("John", "Doe")
.age(30)
.phone("123-456-7890")
.address("123 Main St")
.build();
System.out.println(person1);
Person person2 = new Person.Builder("Jane", "Smith")
.age(25)
// phone 和 address 使用默认值
.build();
System.out.println(person2);
try {
Person person3 = new Person.Builder(null, "Test")
.build();
} catch (IllegalArgumentException e) {
System.err.println("Error creating person3: " + e.getMessage());
}
}
}
与工厂模式的区别:
-
工厂模式 (Factory Pattern) :通常关注于创建单个对象,客户端通过调用工厂方法直接获得产品实例,不关心创建过程的细节。它强调的是"你需要什么类型的产品,我直接给你"。
-
建造者模式 (Builder Pattern) :关注于创建复杂对象 的构建过程。客户端参与到对象的构建步骤中(或者通过指挥者间接参与),可以分步构建,并最终得到一个完整的复杂对象。它强调的是"我们一步步来构建这个复杂的东西"。
总结:
建造者模式是一种强大的创建型模式,特别适用于构建具有多个组成部分、配置复杂或有多个可选参数的对象。它通过将构建逻辑与对象表示分离,提高了代码的封装性、可读性和可扩展性。链式调用的变体使得对象的创建过程更加流畅和易于理解。