文章目录
- 0.个人感悟
- 建造者模式
- 1. 概念
- 2. 适配场景(什么场景下使用)
- 3. 实现方法
- 3.1 实现思路
- 3.2 UML类图
- 4. 代码示例
- 5. 优缺点
- 6. 源码分析-JDK中的StringBuilder实现分析
- 7. vs抽象工厂模式
0.个人感悟
- 建造者模式也是很典型的创建型设计模式。主要目的是将对象的构建和表示分离:构建角色就负责构建过程(建造者 指挥者),对象(产品)只表示自身属性
- 建造者模式很能表现编程的一个思想:面向接口编程。我们面向对象来建模,抽取不同的角色进行分工,角色和角色之间依赖抽象,方便具体实现的替换,比如这里的具体建造者
- 截止到目前,创建型设计模式复习完了。整体而言,目的都是创建对象,根据不同的情况来选择不同的模式,从而尽量达到1核(高内聚低耦合)4性(复用性 可读性 维护性 稳定性)7大原则(设计原则)
- 建造者模式很容易联想到链式调用,比如常用的lombok @Builder注解,可以提高开发效率,有兴趣可以详细了解下原理和使用方法
- 建议实际工作中多尝试使用设计模式,不怕犯错
- 祝大家元旦快乐
建造者模式
1. 概念
英文定义(摘自《设计模式:可复用面向对象软件的基础》):
Separate the construction of a complex object from its representation so that the same construction process can create different representations.
中文翻译:
将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。
理解:
- 将复杂对象的构建过程与最终产品解耦
2. 适配场景(什么场景下使用)
适合的场景:
- 创建复杂对象: 当对象包含多个组成部分,且这些部分需要按特定顺序或规则组合时
- 构建过程需要精细化控制: 需要分步骤、分阶段构建对象,且构建过程可能变化
- 产品有多个变体: 同一个构建过程需要产生不同表示的产品
- 分工合作:产品的创建和产品的使用由不同的模块、责任人负责,天然契合建造者模式
常见的具体场景举例:
- 配置对象的构建(如数据库连接配置)
- 文档生成器(HTML、PDF等不同格式)
- 套餐组合
- 复杂查询构建
- 游戏角色创建系统
3. 实现方法
3.1 实现思路
- 分离关注点: 构建过程由指挥者(Director)控制,具体构建由建造者(Builder)实现
- 构建与表示分离: 同样的构建步骤可以产生不同的产品表示
- 分步构建: 允许分步骤、精细化地构建复杂对象
- 控制反转: 客户端不直接创建对象,而是通过指挥者间接创建
3.2 UML类图
- 产品(Product): 创建要构建的复杂对象,包含所有组成部分
- 建造者接口(Builder): 声明创建产品各个部分的抽象方法
- 具体建造者类(ConcreteBuilder):
- 实现Builder接口
- 维护当前产品的表示
- 提供获取最终产品的方法
- 指挥者(Director):
- 封装构建过程
- 使用Builder接口逐步构建产品
- 客户端: 客户端创建具体建造者,将其传递给指挥者,然后获取最终产品
4. 代码示例
以互联网大厂夜宵文化为例:,简化一下流程,规定夜宵套餐包含 水果 饮料 主食,忽略金额
产品类:
publicclassNightSnack{privateStringfruit;privateStringdrink;privateStringstapleFood;publicStringgetFruit(){returnfruit;}// 省略}建造者类族:
// 1.抽象建造者publicabstractclassSnackBuilder{NightSnacknightSnack=newNightSnack();publicabstractvoidbuildFruit();publicabstractvoidbuildDrink();publicabstractvoidbuildStapleFood();publicNightSnackgetNightSnack(){returnnightSnack;}}// 2.指挥者publicclassSnackStall{privateSnackBuildersnackBuilder;publicSnackStall(SnackBuildersnackBuilder){this.snackBuilder=snackBuilder;}publicNightSnackconstruct(){// 水果snackBuilder.buildFruit();// 饮料snackBuilder.buildDrink();// 主食snackBuilder.buildStapleFood();returnsnackBuilder.getNightSnack();}}// 3.具体建造者 套餐ApublicclassSnackAextendsSnackBuilder{@OverridepublicvoidbuildFruit(){nightSnack.setFruit("西瓜一个");}@OverridepublicvoidbuildDrink(){nightSnack.setDrink("可乐一瓶");}@OverridepublicvoidbuildStapleFood(){nightSnack.setStapleFood("螺蛳粉一碗");}}// 4.具体建造者 套餐BpublicclassSnackBextendsSnackBuilder{@OverridepublicvoidbuildFruit(){nightSnack.setFruit("酸橘子一筐");}@OverridepublicvoidbuildDrink(){nightSnack.setDrink("外星人饮品一瓶");}@OverridepublicvoidbuildStapleFood(){nightSnack.setStapleFood("面包一块");}}客户端:
publicclassClient{staticvoidmain(){SnackBuildersnackBuilder=newSnackA();// 这里可以进行套餐更换SnackStallsnackStall=newSnackStall(snackBuilder);NightSnacknightSnack=snackStall.construct();System.out.println(STR."套餐内容: \{nightSnack.getFruit()} \{nightSnack.getDrink()} \{nightSnack.getStapleFood()}");}}5. 优缺点
结合1核(高内聚低耦合)4性(复用性 可读性 维护性 稳定性)7大原则(设计原则):
优点:
- 高内聚低耦合
- 构建过程封装在指挥者中,与具体建造者解耦
- 产品与构建过程分离,符合单一职责原则
- 复用性
- 同样的构建过程可以创建不同表示的产品
- 指挥者代码可以复用,支持不同建造者
- 可读性
- 分步骤构建,逻辑清晰
- 维护性
- 新增产品变体只需添加新的建造者,无需修改现有代码
- 符合开闭原则
- 稳定性
- 可以确保产品的完整性,避免构建不完整的对象
- 构建过程可控,减少错误
缺点:
- 复杂度增加
- 需要创建多个类(Builder、Director、Product等)
- 对于简单对象,可能显得过于复杂
- 性能开销
- 相比直接创建对象,有额外的对象创建和方法调用开销。这其实是设计模式的通病,灵活性的代价
- 灵活性受限
- 产品必须有共同的接口,限制了产品类型的多样性。当产品差异性很大时,不推荐使用
6. 源码分析-JDK中的StringBuilder实现分析
主要关注append方法
StringBuilder源码简化分析:
publicfinalclassStringBuilderextendsAbstractStringBuilder{// 1. 存储产品(字符串)的字符数组// char[] value; // 继承自AbstractStringBuilderpublicStringBuilder(){super(16);// 初始容量}publicStringBuilder(intcapacity){super(capacity);}publicStringBuilder(Stringstr){super(str.length()+16);append(str);// 初始内容}// 2. 建造者方法 - 添加部件@OverridepublicStringBuilderappend(Stringstr){super.append(str);returnthis;// 关键:返回this实现链式调用}@OverridepublicStringBuilderappend(charc){super.append(c);returnthis;}@OverridepublicStringBuilderappend(inti){super.append(i);returnthis;}// 3. 获取最终产品@OverridepublicStringtoString(){// 创建String对象,使用当前的字符数组returnnewString(value,0,count);}}}AbstractStringBuilder中的append:
abstractsealedclassAbstractStringBuilderimplementsAppendable,CharSequencepermitsStringBuilder,StringBuffer{publicAbstractStringBuilderappend(Stringstr){if(str==null){returnappendNull();}intlen=str.length();ensureCapacityInternal(count+len);putStringAt(count,str);count+=len;returnthis;}}测试:
publicclassStringTest{staticvoidmain(String[]args){// 典型的建造者模式使用方式Stringresult=newStringBuilder().append("Hello")// 构建第一部分.append(" ")// 构建第二部分.append("World")// 构建第三部分.append("!")// 构建第四部分.reverse()// 对构建结果进行操作.toString();// 获取最终产品System.out.println(result);// 输出: !dlroW olleH}}可以看到StringBuilder中的建造者模式特点:
- 内聚的建造者: StringBuilder同时承担了建造者和指挥者的职责
- 链式调用: 每个构建方法返回
this,支持流畅接口(Fluent Interface)。这个在实际工作中常用Lombok的 @Builder注解 - 延迟创建: 只有在调用
toString()时才创建最终的String对象
7. vs抽象工厂模式
关键区别总结:
- 建造者模式: 关注如何构建一个复杂对象,构建过程是重点。粒度更细,可以理解为车间维度
- 抽象工厂模式: 关注创建什么产品,产品家族是重点。粒度更粗,可以理解为工厂维度
实际选择经验建议:
- 当需要创建的对象结构复杂、构建步骤多时,使用建造者模式
- 当需要创建一系列相关对象时,使用抽象工厂模式
- 在某些复杂场景下,两种模式可以结合使用,一个粗粒度,一个细粒度
参考:
- 韩顺平 Java设计模式
- 张维鹏 Java设计模式之创建型:建造者模式)
- 设计模式实战——开发中经常涉及到的建造者模式