写软件的时候,你一定遇到过这种"长得吓人"的构造函数:一个对象要初始化一大堆属性,构造方法动不动十几个参数,而且有些是必填,有些是可选,有些之间还有约束关系。每次 new 的时候,你都要对着文档一个个数参数,生怕顺序写错或者忘了哪个必填项。
最典型的例子:你要创建一个"用户配置"对象 UserConfig,里面有用户名、年龄、性别、是否接收通知、通知频率、主题颜色、语言、时区......等等。
如果你一开始图省事,很容易写出这样的构造函数:
public class UserConfig {
private String userName;
private int age;
private String gender;
private boolean enableNotify;
private int notifyInterval;
private String theme;
private String language;
private String timeZone;
public UserConfig(String userName,
int age,
String gender,
boolean enableNotify,
int notifyInterval,
String theme,
String language,
String timeZone) {
this.userName = userName;
this.age = age;
this.gender = gender;
this.enableNotify = enableNotify;
this.notifyInterval = notifyInterval;
this.theme = theme;
this.language = language;
this.timeZone = timeZone;
}
}
然后在某个地方,你这样创建对象:
UserConfig config = new UserConfig(
"Tom",
18,
"male",
true,
15,
"dark",
"zh_CN",
"Asia/Shanghai"
);
你盯着这一堆参数,心里只有一个想法:**看得头皮发麻**。哪怕只过了一周,你再回来看,也几乎要重新对着构造函数一个个对照:这个 true 是不是 enableNotify?15 是不是 notifyInterval?"dark" 是不是 theme?只要一不小心调换了顺序,你就埋下了一个相当隐蔽的 bug。
leader 看你在构造函数参数里挣扎了几次后,终于忍不住说:"这就是典型的'构造函数爆炸'问题,适合用建造者模式来解决。"
一、先把"复杂对象"的构建拆分成"步骤"
leader 让你先想一想:创建一个 UserConfig,真的需要在一行构造函数里一次性搞定所有属性吗?
你很快意识到,其实你平时是这样在脑子里"构建"这个对象的:
-
先确定用户名、年龄、性别这些"基础信息";
-
然后再看用户是否开启通知,如果是,就设置通知间隔;
-
最后选择主题、语言、时区这些"偏好设置"。
也就是说,**你在思维上是"分步骤"构建这个对象的,而不是一次性把所有参数砸进去**。建造者模式做的事情,就是把你脑子里的"构建流程"用代码表达出来。
于是你决定先写一个 Builder 类:
public class UserConfig {
private String userName;
private int age;
private String gender;
private boolean enableNotify;
private int notifyInterval;
private String theme;
private String language;
private String timeZone;
private UserConfig() {
}
public static class Builder {
private String userName;
private int age;
private String gender;
private boolean enableNotify;
private int notifyInterval;
private String theme = "light";
private String language = "zh_CN";
private String timeZone = "Asia/Shanghai";
public Builder setUserName(String userName) {
this.userName = userName;
return this;
}
public Builder setAge(int age) {
this.age = age;
return this;
}
public Builder setGender(String gender) {
this.gender = gender;
return this;
}
public Builder setEnableNotify(boolean enableNotify) {
this.enableNotify = enableNotify;
return this;
}
public Builder setNotifyInterval(int notifyInterval) {
this.notifyInterval = notifyInterval;
return this;
}
public Builder setTheme(String theme) {
this.theme = theme;
return this;
}
public Builder setLanguage(String language) {
this.language = language;
return this;
}
public Builder setTimeZone(String timeZone) {
this.timeZone = timeZone;
return this;
}
public UserConfig build() {
UserConfig config = new UserConfig();
config.userName = this.userName;
config.age = this.age;
config.gender = this.gender;
config.enableNotify = this.enableNotify;
config.notifyInterval = this.notifyInterval;
config.theme = this.theme;
config.language = this.language;
config.timeZone = this.timeZone;
return config;
}
}
}
现在创建 UserConfig 的代码可以写成这样:
UserConfig config = new UserConfig.Builder()
.setUserName("Tom")
.setAge(18)
.setGender("male")
.setEnableNotify(true)
.setNotifyInterval(15)
.setTheme("dark")
.setLanguage("zh_CN")
.setTimeZone("Asia/Shanghai")
.build();
这一看就比一长串构造函数参数清晰多了:**每个参数后面跟着"名字"**,你一眼就知道自己在设置什么。而且,很多可选项还可以不用设置,直接用 Builder 里定义好的默认值。
leader 看了之后说:"这就是典型的建造者简化版------Builder 放在类内部,负责一步步'组装' UserConfig。"
二、建造者模式的标准角色划分
你继续查资料,发现教科书里的建造者模式通常会提到四个角色:
-
产品(Product):要被构建的复杂对象(这里就是 UserConfig);
-
抽象建造者(Builder):规定如何一步步构建产品的接口(上面例子可以是接口或抽象类);
-
具体建造者(ConcreteBuilder):实现具体的构建步骤;
-
指挥者(Director):负责安排构建顺序,指导 Builder 如何一步步构建产品。
于是你尝试用更"教科书"的方式写一遍:
// 产品
public class Computer {
String cpu;
String ram;
String disk;
String gpu;
@Override
public String toString() {
return "CPU=" + cpu + ", RAM=" + ram
+ ", DISK=" + disk + ", GPU=" + gpu;
}
}
// 抽象建造者
public interface ComputerBuilder {
void buildCPU();
void buildRAM();
void buildDisk();
void buildGPU();
Computer getResult();
}
// 具体建造者:高配电脑
public class HighEndComputerBuilder implements ComputerBuilder {
private Computer computer = new Computer();
@Override
public void buildCPU() {
computer.cpu = "i9";
}
@Override
public void buildRAM() {
computer.ram = "32G";
}
@Override
public void buildDisk() {
computer.disk = "1T SSD";
}
@Override
public void buildGPU() {
computer.gpu = "RTX 4090";
}
@Override
public Computer getResult() {
return computer;
}
}
// 具体建造者:办公电脑
public class OfficeComputerBuilder implements ComputerBuilder {
private Computer computer = new Computer();
@Override
public void buildCPU() {
computer.cpu = "i5";
}
@Override
public void buildRAM() {
computer.ram = "16G";
}
@Override
public void buildDisk() {
computer.disk = "512G SSD";
}
@Override
public void buildGPU() {
computer.gpu = "集成显卡";
}
@Override
public Computer getResult() {
return computer;
}
}
// 指挥者
public class ComputerDirector {
private ComputerBuilder builder;
public ComputerDirector(ComputerBuilder builder) {
this.builder = builder;
}
public Computer construct() {
builder.buildCPU();
builder.buildRAM();
builder.buildDisk();
builder.buildGPU();
return builder.getResult();
}
}
客户端使用:
public class Client {
public static void main(String[] args) {
// 高配电脑
ComputerBuilder highBuilder = new HighEndComputerBuilder();
ComputerDirector director = new ComputerDirector(highBuilder);
Computer highEnd = director.construct();
System.out.println("高配电脑:" + highEnd);
// 办公电脑
ComputerBuilder officeBuilder = new OfficeComputerBuilder();
director = new ComputerDirector(officeBuilder);
Computer office = director.construct();
System.out.println("办公电脑:" + office);
}
}
在这个例子里,你可以很清楚地看到建造者模式解决的是两个问题:
-
**复杂对象的"构建过程"被拆分为多个步骤**(buildCPU、buildRAM、buildDisk、buildGPU);
-
**同样的构建步骤顺序,可以通过不同具体建造者,构建出"不同配置"的产品**。
三、建造者模式的正式定义
经过上面的例子,你可以给出一个比较严谨的定义:
建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
这里几个关键词很重要:
-
"复杂对象":不是简单的两三个字段,而是包含多个组成部分,并且构建顺序、约束较多;
-
"构建与表示分离":构造步骤(怎么一步步组装)和最终长什么样(高配 / 低配 / 不同风格)分开;
-
"同样的构建过程可以创建不同的表示":指挥者可以不变,只切换具体建造者,就能得到不同产品。
四、建造者模式的优点与缺点
照例,leader 又让你总结一遍优缺点。
优点:
-
**让构建过程更清晰可控**:复杂对象的构建步骤被拆分、显式化,更容易理解和维护。
-
**支持不同表示的复用构建过程**:同样的一套构建流程,可以生成不同版本的产品(高配 / 低配)。
-
**便于控制不变性和约束**:在 build() 里集中做合法性校验,保证构建出的对象总是处于合法状态。
-
**代码可读性提升明显**:像 UserConfig 这种参数很多的类,用 Builder 写法要比长构造函数清晰太多。
缺点:
-
**结构相对复杂**:类的数量增加了,引入 Builder、Director 等角色,对小项目有点"杀鸡用牛刀"。
-
**不适合特别简单的对象构建**:如果只有两三个字段,直接构造函数或工厂方法更简单。
-
**容易和工厂模式混淆**:都是在"创建对象",但关心的重点不同,使用时要分清场景。
leader 总结说:"建造者模式更关注'一步步怎么创建',工厂模式更关注'给你哪一种成品'。"
五、建造者模式和工厂模式的区别
你顺势问 leader:"都是创建对象,工厂模式和建造者模式到底啥区别?"
leader 给你举了一个非常形象的比喻:
-
**工厂模式**:你走进手机店,说"给我一台某型号手机",店员直接把成品拿给你。你不关心它是怎么被组装出来的。
-
**建造者模式**:你走进一个"定制电脑"店,说"我要一个游戏本",店员会跟你聊 CPU、内存、硬盘、显卡,一步步帮你选配,最后再组装成一台电脑。
换成代码层面:
-
工厂模式更适合"简单对象",或者你只在意"要哪一种成品",而不关心构造过程;
-
建造者模式更适合"内部组成复杂、属性嵌套多、构建过程可以拆成多个步骤"的对象。
所以,**当你只是在选择"哪一款现成的产品"时,用工厂模式;当你在"配置一台定制产品"时,用建造者模式。**
六、建造者模式的典型应用场景
你回顾自己写过的代码,发现很多地方其实都可以用建造者模式来改善:
- **具有大量可选参数的对象**
例如 HTTP 请求构造、数据库连接配置、线程池参数等,很多框架都用 Builder 模式来构建这些对象。
- **对象构建过程中有复杂的校验逻辑**
比如订单对象,金额不能为负、币种不能为空、下单时间不能晚于支付时间......这些都可以放在 build 里统一校验。
- **需要根据不同策略构建不同"版本"的产品**
比如不同套餐、不同等级、不同地区的配置对象,可以通过不同的 Builder 实现来定制。
- **复杂 UI 组件的搭建**
有些 UI 库会用 Builder 来一步步添加子组件、设置布局、注册事件,最后一次性 build 出一个复杂界面。
你这才意识到,自己其实早就在不自觉地模仿建造者模式,只是以前写的是"半成品",现在通过系统梳理,终于把这套做法变成了"有名字的套路"。
七、建造者模式小结
最后,leader 又送了你一句话,帮你牢牢记住建造者模式的使用场景:
当你创建某个对象时,发现构造函数参数已经长到看不清头尾,或者构造步骤需要严格控制顺序、并带有复杂约束时,就该考虑用建造者模式,把"怎么一步步创建"抽离出来,让调用方用一种自然、可读的方式去搭建这个复杂对象。
建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。