java设计模式之建造者模式《装修启示录》
周五的早上,项目经理小白还沉浸在即将到达的假期的喜悦中,喝着9块9的瑞幸咖啡畅想人生时,老板突然拍出一张装修设计图:"小~白~啊(此处请脑补领导拉长音),新办公室装修就交给你了!年轻人要多锻炼!公司不提倡加班,周一早上弄好给我就行" 话没说完,老板都走远了。
小白连忙放下手中的咖啡,揣着设计图看了起来,原来新办公室要分成三个区域。
- 开放办公区:要体现"互联网狼性文化"(翻译:桌子挤到放不下水杯)
- 高管会议室:必须彰显"行业领军地位"(翻译:椅子要比老板现在的再贵2000)
- 茶水间:得营造"家一般的温暖"(翻译:预算砍半但效果要媲美星巴克)
这时候,小白脑中已经构想出来了实施方案:
// 装修团队
class DecorationTeam {
// areaType 装修的区域
public void decorate(String areaType) {
// 开放办公区
if ("OPEN_OFFICE".equals(areaType)) {
buyChairs(); // 批发椅子
installCheapWifi(); // 装最便宜的WiFi
crampDesks(); // 把桌子塞成沙丁鱼罐头
// 高管会议室or 老板 会议室
} else if ("BOSS_ROOM".equals(areaType)) {
importItalianChair(); // 进口意大利真皮椅
topWifi(); // 企业级万兆WiFi
chenxiangmuDesks(); //沉香木桌子
addFakeArt(); // 挂淘宝99包邮的抽象画
}
}
}
经过一早上的努力,实施方案已经出来了,小白翘着二郎腿,又开始美滋滋幻想放假的日子了。但是,下午行政小妹又来传达BOOS的意思,要再划分个客户接待室。临时该需求,这一幕可太熟悉了,一顿哐哐下来,施工图又改好了。
// 装修团队
class DecorationTeam {
// areaType 装修的区域
public void decorate(String areaType) {
// 开放办公区
if ("OPEN_OFFICE".equals(areaType)) {
buyChairs(); // 批发椅子
installCheapWifi(); // 装最便宜的WiFi
crampDesks(); // 把桌子塞成沙丁鱼罐头
// 高管会议室or 老板 会议室
} else if ("BOSS_ROOM".equals(areaType)) {
importItalianChair(); // 进口意大利真皮椅
topWifi(); // 企业级万兆WiFi
chenxiangmuDesks(); //沉香木桌子
addFakeArt(); // 挂淘宝99包邮的抽象画
// 客户接待室
} else if ("CUSTOMER_ROOM".equals(areaType)) {
normalChairs(); // 普通座椅
100mwifi(); // 100兆wifi
officeDesks(); //办公型桌子
}
}
}
但是小白这次并没有松懈,因为有句老话说得好:改需求只有第一次跟无数次。
果然,临近下班的时候,微信传来了老板的信息:小白,最近的AI那么火,再加个AI创意室吧,很快的,改改就好了。
已经红温的小白经过1秒的内心平复后:好的,老板。
进过一晚上的加班,新一版的施工图纸又出来了。
// 装修团队
class DecorationTeam {
// areaType 装修的区域
public void decorate(String areaType) {
// 开放办公区
if ("OPEN_OFFICE".equals(areaType)) {
buyChairs(); // 批发椅子
installCheapWifi(); // 装最便宜的WiFi
crampDesks(); // 把桌子塞成沙丁鱼罐头
// 高管会议室or 老板 会议室
} else if ("BOSS_ROOM".equals(areaType)) {
importItalianChair(); // 进口意大利真皮椅
topWifi(); // 企业级万兆WiFi
chenxiangmuDesks(); //沉香木桌子
addFakeArt(); // 挂淘宝99包邮的抽象画
// 客户接待室
} else if ("BOSS_ROOM".equals(areaType)) {
normalChairs(); // 普通座椅
100mwifi(); // 100兆wifi
officeDesks(); //办公型桌子
// AI创意室
}else if ("AI_ROMM".equals(areaType)) {
smartChairs(); // 智能椅子
smartWifi(); // 智能WIFI
smartDesks(); // 智能桌子
}
}
}
小白揣着施工图看了一会,看着满屏重复的if-else
和重复代码,小白突然发现:
- 每次新增区域都要修改核心类,迟早变成"屎山代码"
- 客户接待室的判断条件居然写错成BOSS_ROOM(复制粘贴惹的祸)
- 智能桌子的配置参数和普通桌子完全混在一起
这样子可不行呀,如果后续再增加一个临时办公室,又得一堆if else , 虽然现在流行防御性编程,但是也得看是谁维护啊,自己维护还防御个啥。。。。小白在苦思冥想解决方案时,发现被压在桌底的一本书,隐隐约约露出<设计模式>四个字,小白把它抽了出来,拍了拍灰尘,还好,没被老鼠咬坏。打开书,扫下了所有的设计模式,结合当前的业务
(所有房间所需要的 椅子类型、WiFi配置、桌子风格 都是固定的)那使用建造者模式不就好了。说干就干。干之前先回顾下
什么是建造者模式?
把一个复杂对象的构建过程拆解成多个步骤,通过链式调用逐步组装参数,最终像搭积木一样创建出灵活配置的对象。
核心三要素
产品(Product):最终要创建的复杂对象(如装修方案、电脑配置)
建造者(Builder):定义组件的接口 + 实现具体构建步骤(如.setChair()、.setWifi())
指挥者(Director):可选 封装常用配置模板(如一键生成"狼性文化工位"模板)
理解了该设计模式后,小白信心十足,就着手开始修改。
装修产品类(核心配置)
class OfficeArea {
private String areaType; // 区域类型
private String chairType; // 椅子类型
private String wifiSpec; // WiFi配置
private String deskStyle; // 桌子风格
private String specialNote; // 老板的特殊要求批注
// 私有构造方法,禁止直接new
private OfficeArea() {}
// 显示装修方案
public void showPlan() {
System.out.println("\n=== " + areaType + "装修方案 ===");
System.out.println("椅子类型:" + chairType);
System.out.println("WiFi配置:" + wifiSpec);
System.out.println("桌子风格:" + deskStyle);
if(specialNote != null) {
System.out.println("老板批注:" + specialNote);
}
}
// 建造者(核心)
static class Builder {
private OfficeArea area = new OfficeArea();
// 必须指定区域类型
public Builder(String type) {
area.areaType = type;
}
// 配置方法链(老板的每个要求对应一个方法)
public Builder setChair(String type) {
area.chairType = type;
return this;
}
public Builder setWifi(String spec) {
area.wifiSpec = spec;
return this;
}
public Builder setDesk(String style) {
area.deskStyle = style;
return this;
}
public Builder bossSaid(String note) {
area.specialNote = note;
return this;
}
// 最终构建方法
public OfficeArea build() {
// 自动补全老板的经典语录
if(area.specialNote == null) {
area.specialNote = "要大气!要省钱!";
}
return area;
}
}
}
装修配置器(封装常用配置)
// 装修配置器(封装常用配置)
class AreaPreset {
// 狼性文化办公区(桌子挤到放不下水杯)
public static OfficeArea wolfCulture() {
return new OfficeArea.Builder("开放办公区")
.setChair("批发塑料椅")
.setWifi("最低配路由器")
.setDesk("间距<30cm的密集排列")
.bossSaid("体现团队拼搏精神")
.build();
}
// 土豪老板会议室(椅子比现在贵2000)
public static OfficeArea bossRoom() {
return new OfficeArea.Builder("高管会议室")
.setChair("意大利进口真皮椅(+2000升级款)")
.setWifi("万兆企业级WiFi")
.setDesk("沉香木会议桌")
.bossSaid("我的椅子要比王总的贵")
.build();
}
}
实践效果
public class BuilderDemo {
public static void main(String[] args) {
// 原有需求快速配置
OfficeArea openArea = AreaPreset.wolfCulture();
openArea.showPlan();
// 突然新增的客户接待室需求(临时配置)
OfficeArea clientLounge = new OfficeArea.Builder("客户接待室")
.setChair("宜家简约款")
.setWifi("百兆商用网络")
.setDesk("弧形协作桌")
.bossSaid("看起来要比竞品公司高端")
.build();
clientLounge.showPlan();
// 老板临时加的AI创意室(配置灵活扩展)
OfficeArea aiRoom = new OfficeArea.Builder("AI创意室")
.setChair("电竞人体工学椅")
.setWifi("WiFi6+蓝牙Mesh")
.setDesk("智能升降桌")
.bossSaid("要能拍抖音的那种科技感")
.build();
aiRoom.showPlan();
}
}
优势分析
- 新增区域类型时只需新建Builder配置,无需碰核心类代码
- 各区域的配置参数完全隔离,不会出现误修改
- 支持临时魔改配置(比如给茶水间加个电竞椅)
- 可以复用现有配置(如
AreaPreset
中的预设方案)
劣势分析
- 代码复杂度增加(每个产品类都需要配套Builder类)
- 对象属性变更成本高(产品类的属性变化需要同步修改Builder类)
- 运行时校验困难
public Phone build() { if(ram == null) throw new Exception(); // 只能在build时校验 }
使用场景
- 参数多且存在组合需求
// 电商商品SKU系统 new SkuBuilder() .id("GX-2038") .color("星空灰") .size("XL") .material("纯棉") .batch("2023A") .build();
- 需要分步构造复杂对象
// 构建游戏角色 Character hero = new CharacterBuilder() .setBaseInfo("勇者", 1) .equipWeapon("圣剑") .learnSkill("旋风斩") .mountPet("黄金狮鹫") .build();
- 要求对象不可变
// 线程安全的配置对象 class AppConfig { private final String dbUrl; private final int threadPoolSize; // 只能通过Builder设置final字段 private AppConfig(Builder builder) { /*...*/ } }
- 需要多种对象变体
// 快餐套餐组合 Meal kidsMeal = Meal.builder() // 儿童套餐 .main("小汉堡") .drink("牛奶") .toy("卡通玩偶") .build(); Meal businessMeal = Meal.builder() // 商务套餐 .main("牛排") .drink("红酒") .discountCoupon() .build();
终章
当小白提交完最后一行代码,窗外已是凌晨三点的月光。他望着屏幕上优雅的建造者配置链,突然领悟到一个真理:
**"这世上本没有需求黑洞,老板的脑洞大了,便成了黑洞。"**