介绍
- 建造者模式又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同属性的对象
- 建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,不需要知道内部的具体构建细节(如用户在选购汽车的时候,只需要选择好方向盘、轮胎、发动机类型,不需要知道零件是怎么制造出来的)
出场角色
Product(产品)
:一个具体的产品对象Builder (抽象建造者)
: 创建指定roduct对象的各个部件的接口/抽象类,用于将建造流程整理出来,但是不在乎建造细节,即只声明抽象方法ConcreteBuilder (具体建造者)
: 实现抽象建造者的接口Director(指挥者/监工)
: 使用Builder接口来生成示例。它主要是用于创建一个复杂的对象。它主要有两个作用,一是隔离客户与对象的生产过程,二是控制产品对象的生产过程。Client(使用者)
:让Director干活,使用建造者模式
案例
盖房子需求
房子有多种类型,比如普通房,高楼,别墅,建房子的过程包括打桩
、砌墙
、封顶
多个步骤,不同类型的房子建造的流程差不多,但是里面的方法有所差异
原始实现
【抽象房子类】
package com.atguigu.builder;
public abstract class AbstractHouse {
/**
* 打地基
*/
public abstract void buildBasic();
/**
* 砌墙
*/
public abstract void buildWalls();
/**
* 封顶
*/
public abstract void roofed();
public void build() {
buildBasic();
buildWalls();
roofed();
}
}
【普通房子】
package com.atguigu.builder;
public class CommonHouse extends AbstractHouse {
@Override
public void buildBasic() {
System.out.println(" 普通房子打地基 ");
}
@Override
public void buildWalls() {
System.out.println(" 普通房子砌墙 ");
}
@Override
public void roofed() {
System.out.println(" 普通房子封顶 ");
}
}
【高楼】
package com.atguigu.builder;
public class HignBuilding extends AbstractHouse {
@Override
public void buildBasic() {
System.out.println(" 高楼打地基 ");
}
@Override
public void buildWalls() {
System.out.println(" 高楼房子砌墙 ");
}
@Override
public void roofed() {
System.out.println(" 高楼房子封顶 ");
}
}
【主类】
package com.atguigu.builder;
public class Client {
public static void main(String[] args) {
CommonHouse commonHouse = new CommonHouse();
commonHouse.build();
System.out.println("-------------------------------------------------------------------------------------------------------");
HignBuilding highBuilding = new HignBuilding();
highBuilding.build();
}
}
【运行】
普通房子打地基
普通房子砌墙
普通房子封顶
-------------------------------------------------------------------------------------------------------
高楼打地基
高楼房子砌墙
高楼房子封顶
Process finished with exit code 0
分析
- 优点:比较好理解,简单易操作
- 缺点:程序结构过于简单,没有设计缓存层对象,程序的扩展和维护不好。这种设计方案,把产品(即:房子) 和 创建产品的过程(即:建房子流程) 封装在一起,耦合性增强了
- 改进:使用建造者模式,将产品和产品建造过程解耦
建造者模式
【抽象房子】
package com.atguigu.builder.improve;
/**
* 产品->Product
*/
public class House {
private String basic;
private String wall;
private String roofed;
public String getBasic() {
return basic;
}
public void setBasic(String basic) {
this.basic = basic;
}
public String getWall() {
return wall;
}
public void setWall(String wall) {
this.wall = wall;
}
public String getRoofed() {
return roofed;
}
public void setRoofed(String roofed) {
this.roofed = roofed;
}
public House(String basic, String wall, String roofed) {
this.basic = basic;
this.wall = wall;
this.roofed = roofed;
}
public House() {
}
@Override
public String toString() {
return "House{" +
"basic='" + basic + '\'' +
", wall='" + wall + '\'' +
", roofed='" + roofed + '\'' +
'}';
}
}
【抽象建造者】
package com.atguigu.builder.improve;
/**
* 抽象的建造者
*/
public abstract class HouseBuilder {
/**
* 组合House
*/
protected House house = new House();
//-------------------------将建造的流程写好--------------------------
/**
* 打地基
*/
public abstract void buildBasic();
/**
* 砌墙
*/
public abstract void buildWalls();
/**
* 封顶
*/
public abstract void roofed();
/**
* 建造好房子后将产品(房子) 返回
* @return
*/
public House buildHouse() {
return house;
}
}
【具体建造者:普通房】
package com.atguigu.builder.improve;
/**
* 具体建造者
*/
public class CommonHouse extends HouseBuilder {
@Override
public void buildBasic() {
System.out.println(" 普通房子打地基5米 ");
super.house.setBasic("地基5米");
}
@Override
public void buildWalls() {
System.out.println(" 普通房子砌墙10cm ");
super.house.setWall("墙10cm");
}
@Override
public void roofed() {
System.out.println(" 普通房子屋顶 ");
super.house.setRoofed("普通房子屋顶");
}
}
【具体建造者:高楼】
package com.atguigu.builder.improve;
/**
* 具体建造者
*/
public class HighBuilding extends HouseBuilder {
@Override
public void buildBasic() {
System.out.println(" 高楼的打地基100米 ");
super.house.setBasic("地基100米");
}
@Override
public void buildWalls() {
System.out.println(" 高楼的砌墙20cm ");
super.house.setWall("墙20cm");
}
@Override
public void roofed() {
System.out.println(" 高楼的透明屋顶 ");
super.house.setRoofed("透明屋顶");
}
}
【指挥者】
package com.atguigu.builder.improve;
/**
* 指挥者,调用制作方法,返回产品
*/
public class HouseDirector {
/**
* 聚合
*/
HouseBuilder houseBuilder = null;
/**
* 方式一:构造器传入 houseBuilder
* @param houseBuilder
*/
public HouseDirector(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
/**
* 方式二:通过setter 传入 houseBuilder
* @param houseBuilder
*/
public void setHouseBuilder(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
/**
* 指挥者统一管理建造房子的流程
* @return
*/
public House constructHouse() {
houseBuilder.buildBasic();
houseBuilder.buildWalls();
houseBuilder.roofed();
return houseBuilder.buildHouse();
}
}
【客户端】
package com.atguigu.builder.improve;
public class Client {
public static void main(String[] args) {
///盖普通房子
//准备创建房子的指挥者
HouseDirector houseDirector = new HouseDirector(new CommonHouse());
//完成盖房子,返回产品(普通房子)
House commonHouse = houseDirector.constructHouse();
System.out.println("普通房子:" + commonHouse.toString());
System.out.println("---------------------------------------------------------------------------------------------");
///盖高楼
//重置建造者,改成修高楼
houseDirector.setHouseBuilder(new HighBuilding());
//完成盖房子,返回产品(高楼)
House highBuilding = houseDirector.constructHouse();
System.out.println("高楼:" + highBuilding.toString());
}
}
【运行】
普通房子打地基5米
普通房子砌墙10cm
普通房子屋顶
普通房子:House{basic='地基5米', wall='墙10cm', roofed='普通房子屋顶'}
---------------------------------------------------------------------------------------------
高楼的打地基100米
高楼的砌墙20cm
高楼的透明屋顶
高楼:House{basic='地基100米', wall='墙20cm', roofed='透明屋顶'}
Process finished with exit code 0
文档编写
【抽象建造者】
package com.atguigu.builder.Sample;
public abstract class Builder {
/**
* 编写标题
* @param title
*/
public abstract void makeTitle(String title);
/**
* 编写字符串
* @param str
*/
public abstract void makeString(String str);
/**
* 编写条目
* @param items
*/
public abstract void makeItems(String[] items);
/**
* 完成文档编写
*/
public abstract void close();
}
【具体建造者一】
package com.atguigu.builder.Sample;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class HTMLBuilder extends Builder {
/**
* 文件名
*/
private String filename;
/**
* 用于编写文件的PrintWriter
*/
private PrintWriter writer;
/**
* HTML文件的标题
*
* @param title
*/
public void makeTitle(String title) {
// 将标题作为文件名
filename = title + ".html";
try {
// 生成 PrintWriter
writer = new PrintWriter(new FileWriter(filename));
} catch (IOException e) {
e.printStackTrace();
}
// 输出标题
writer.println("<html><head><title>" + title + "</title></head><body>");
writer.println("<h1>" + title + "</h1>");
}
/**
* HTML文件中的字符串
*
* @param str
*/
public void makeString(String str) {
// 用<p>标签输出
writer.println("<p>" + str + "</p>");
}
/**
* HTML文件中的条目
*
* @param items
*/
public void makeItems(String[] items) {
// 用<ul>和<li>输出
writer.println("<ul>");
for (int i = 0; i < items.length; i++) {
writer.println("<li>" + items[i] + "</li>");
}
writer.println("</ul>");
}
/**
* 完成文档
*/
public void close() {
// 关闭标签
writer.println("</body></html>");
// 关闭文件
writer.close();
}
public String getResult() {
// 编写完成的文档
return filename;
}
}
【具体建造者二】
package com.atguigu.builder.Sample;
public class TextBuilder extends Builder {
/**
* 文档内容保存在该字段中
*/
private StringBuffer buffer = new StringBuffer();
/**
* 纯文本的标题
*
* @param title
*/
public void makeTitle(String title) {
// 装饰线
buffer.append("==============================\n");
// 为标题添加『』
buffer.append("『" + title + "』\n");
// 换行
buffer.append("\n");
}
/**
* 纯文本的字符串
*
* @param str
*/
public void makeString(String str) {
// 为字符串添加■
buffer.append('■' + str + "\n");
// 换行
buffer.append("\n");
}
/**
* 纯文本的条目
*
* @param items
*/
public void makeItems(String[] items) {
for (int i = 0; i < items.length; i++) {
// 为条目添加・
buffer.append(" ・" + items[i] + "\n");
}
// 换行
buffer.append("\n");
}
/**
* 完成文档
*/
public void close() {
// 装饰线
buffer.append("==============================\n");
}
/**
* 完成的文档
* @return
*/
public String getResult() {
// 将StringBuffer变换为String
return buffer.toString();
}
}
【指挥者】
package com.atguigu.builder.Sample;
/**
* 由于TextBuilder和HTMLBuilder都是Builder的子类,因此Director 仅仅使用Builder的方法即可编写文档。
* Director并不关心实际编写文档的到底是TextBuilder还是HTMBuilder
*/
public class Director {
private Builder builder;
/**
* 实际上不会将 Builder类的实例作为参数传递给 Director类。这是因为 Builder 类是抽象类,是无法生成其实例的。
* 实际上传递给 Director 类的是 Builder类的子类
* @param builder
*/
public Director(Builder builder) {
this.builder = builder;
}
/**
* 编写文档
*/
public void construct() {
// 标题
builder.makeTitle("Greeting");
// 字符串
builder.makeString("从早上至下午");
// 条目
builder.makeItems(new String[]{
"早上好。",
"下午好。",
});
// 其他字符串
builder.makeString("晚上");
// 其他条目
builder.makeItems(new String[]{
"晚上好。",
"晚安。",
"再见。",
});
// 完成文档
builder.close();
}
}
【测试】
package com.atguigu.builder.Sample;
/**
* Builder模式测试
*/
public class Main {
public static void main(String[] args) {
String type = "plain";
if (type.equals("plain")) {
TextBuilder textbuilder = new TextBuilder();
Director director = new Director(textbuilder);
director.construct();
String result = textbuilder.getResult();
System.out.println(result);
} else if (type.equals("html")) {
HTMLBuilder htmlbuilder = new HTMLBuilder();
Director director = new Director(htmlbuilder);
director.construct();
String filename = htmlbuilder.getResult();
System.out.println(filename + "文件编写完成。");
} else {
usage();
System.exit(0);
}
}
public static void usage() {
System.out.println("Usage: java Main plain 编写纯文本文档");
System.out.println("Usage: java Main html 编写HTML文档");
}
}
【运行】
type = “plain”
==============================
『Greeting』
■从早上至下午
・早上好。
・下午好。
■晚上
・晚上好。
・晚安。
・再见。
==============================
Process finished with exit code 0
type = “html”
<html>
<head><title>Greeting</title></head>
<body>
<h1>Greeting</h1>
<p>从早上至下午</p>
<ul>
<li>早上好。</li>
<li>下午好。</li>
</ul>
<p>晚上</p>
<ul>
<li>晚上好。</li>
<li>晚安。</li>
<li>再见。</li>
</ul>
</body>
</html>
【分析】
- Main类并不知道Builder类,它只是调用了Direct 类的construct 方法这样,Director类就会开始工作(Main类对此一无所知),并完成文档的编写
- Director类知道Builder类,它调用Builder类的方法来编写文档,但它并不知道它所使用的类到底是 TextBuilder类、HTMLBuilder类还是其他 Builder类的子类,而且也没有必要知道,因为Director类只使用Builder类的方法
- 不论是将 TextBuilder 的实例传递给 Director,还是将HTMLBuilder类的实例传递给 Director,它都可以正常工作,原因正是Director类不知道Builder类的具体的子类。正是因为不知道才能够替换,正是因为可以替换,组件才具有高价值。作为设计人员,我们必须时刻关注这种“可替换性”
拓展一
【要求】
请修改 Builder类TetBuilder类和HTMLBuilder类,确保“在调用makeString 方法、makeItems 方法和close方法之前必须且只能调用一次makeTitle方法”
【Builder 类】
public abstract class Builder {
private boolean initialized = false;
public void makeTitle(String title) {
if (!initialized) {
buildTitle(title);
initialized = true;
}
}
public void makeString(String str) {
if (initialized) {
buildString(str);
}
}
public void makeItems(String[] items) {
if (initialized) {
buildItems(items);
}
}
public void close() {
if (initialized) {
buildDone();
}
}
protected abstract void buildTitle(String title);
protected abstract void buildString(String str);
protected abstract void buildItems(String[] items);
protected abstract void buildDone();
}
【其他两个类】
其他两个类的方法内容还是一样的,修改一下方法名,将抽象Builder的方法实现即可
问题
问:编写的文档被保存在了buffer字段中,但buffer字段并非是String类型的,而是StringBuffer类型的,请问是为什么呢?如果使用了 string 类型会有什么问题呢?
答:使用String类型保存文档会存在效率问题,因为String类型是不可变的,每次对String进行修改都会生成一个新的String对象,而StringBuffer类型是可变的,可以进行高效的字符串拼接和修改。因此,为了保证文档的高效修改和拼接,使用StringBuffer类型更加合适
JDK源码中使用建造者模式
- Appendable 接口定义了多个append方法(抽象方法),即Appendable 为抽象建造者,定义了抽象方法
- AbstractStringBuilder 实现了 Appendable 接口方法,这里的AbstractStringBuilder 已经是建造者,只是不能实例化
- StringBuilder 即充当了指挥者角色,同时充当了具体的建造者,建造方法的实现是由 AbstractStringBuilder 完成,而StringBuilder 继承了AbstractStringBuilder
【总结】
- JDK中使用设计模式的方式不一定和标准的一样(设计模式可能是更后面提出来的),但是核心思想大同小异
注意事项
- 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
- 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,
用户使用不同的具体建造者即可得到不同的产品对象
- 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程
增加新的具体建造者无须修改原有类库的代码
,指挥者类针对抽象建造者类编程系统扩展方便,符合“开闭原则“- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制
- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式
【抽象工厂模式vs建造者模式】
- 抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品: 具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可
- 建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品
- 两种模式都用来生成复杂的实例
文章说明
- 本文章为本人学习尚硅谷的学习笔记,文章中大部分内容来源于尚硅谷视频(点击学习尚硅谷相关课程),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对尚硅谷的优质课程表示感谢。
- 本人还同步阅读《图解设计模式》书籍(图解设计模式/(日)结城浩著;杨文轩译–北京:人民邮电出版社,2017.1),进而综合两者的内容,让知识点更加全面