7.1 Builder 模式
在建造大楼时,需要先打牢地基,搭建框架,然后自下而上地一层层盖起来。通常,在建造这种具有复杂结构的物体时,很难一气呵成。我们需要首先建造组成这个物体的各个部分,然后分阶段将它们组装起来。
这里,我们将要学习用于组装具有复杂结构的实例的 Builder 模式。
7.2 示例程序
作用:使用 Builder 模式编写 “文档” 的程序,具有以下结构。
- 含有一个标题
- 含有几个字符串
- 含有条目项目
Builder 类中定义了决定文档接口的方法,然后 Director 类使用该方法编写一个具体的文档。
Builder 是抽象类,它并没有进行任何实际的处理,仅仅声明了抽象方法。Builder 类的子类决定了用来编写文档的具体处理。
在示例程序中,我们定义了以下 Builder 类的子类 - TextBuilder 类:使用纯文本编写文档。
- HTMLBuilder 类:使用 HTML 编写文档。
|| Builder 类
Builder 类是一个声明了编写文档的方法的抽象类。
/**
* 编写文档的抽象类
*/
public abstract class Builder {
public abstract void makeTitle(String title);
public abstract void makeString(String str);
public abstract void makeItems(String[] items);
public abstract void close();
}
|| Director 类
Director 类使用 Builder 类中声明的方法来编写文档。
Director 类通过构造方法存储 Buidler 的子类对象。其中 construct 方法是编写文档的方法。其调用的方法都是 Builder 中声明的方法。
/**
* 使用 Builder 类中声明的方法来编写文档
*
*/
public class Director {
private Builder 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();
}
}
|| TextBuilder 类
TextBuilder 类是 Builder 类的子类,其功能是使用纯文本编写文档,并以 String 返回结果。
/**
* 使用纯文本编写文档,并以 String 返回结果
*/
public class TextBuilder extends Builder{
private StringBuilder builder = new StringBuilder();
@Override
public void makeTitle(String title) {
builder.append("===========================\n");
builder.append("『").append(title).append("』\n");
}
@Override
public void makeString(String str) {
builder.append('■').append(str).append("\n");
}
@Override
public void makeItems(String[] items) {
Arrays.asList(items).forEach(e -> builder.append(" .").append(e).append("\n"));
}
@Override
public void close() {
builder.append("===========================\n");
}
public String getResult() {
return builder.toString();
}
}
|| HTMLBuilder 类
HTMLBuilder 类是 Builder 类的子类,它的功能是使用 HTML 编写文档,返回是 HTML 文件的名字。
/**
* 使用 HTML 编写文档
*/
public class HTMLBuilder extends Builder {
private String fileName;
private PrintWriter writer;
@Override
public void makeTitle(String title) {
fileName = title + ".html";
initPrintWriter(fileName);
writer.println("<html><head><title>" + title + "</title></head><body>");
writer.println("<h1>" + title + "</h1>");
}
@Override
public void makeString(String str) {
writer.println("<p>" + str + "</p>");
}
@Override
public void makeItems(String[] items) {
writer.println("<ul>");
Arrays.asList(items).forEach(e -> writer.println("<li>" + e + "</li>"));
writer.println("</ul>");
}
@Override
public void close() {
writer.println("</body></html>");
writer.close();
}
public String getResult() {
return fileName;
}
private void initPrintWriter(String fileName) {
try {
writer = new PrintWriter(new FileWriter(fileName));
} catch (IOException e) {
e.printStackTrace();
System.out.println("创建文件失败");
}
}
}
|| Main 类
Main 类是 Builder 模式的测试程序。当我们在命令行中指定参数为 plain 时,会将 TextBuilder 类的实例作为参数传递至 Director 类的构造方法中;而若是在命令行中指定参数为 HTML 的时候,会将 HTMLBuilder 类的实例作为参数传递至 Director 类的构造方法中。
注意,Director 并不关心实际编写的文档到底是 TextBuilder 还是 HTMLBuilder。
public class Main {
public static void main(String[] args) {
if (args.length != 1) {
usage();
System.exit(0);
}
if (args[0].equals("plain")) {
TextBuilder textBuilder = new TextBuilder();
Director director = new Director(textBuilder);
director.construct();
String result = textBuilder.getResult();
System.out.println(result);
} else if (args[0].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);
}
}
private static void usage() {
System.out.println("Usage: java Main plain 编写纯文本文档");
System.out.println("Usage: java Main html 编写 HTML 文档");
}
}
结果:
1.文本形式
===========================
『Greeting』
■ 从早上至下午
. 早上好
. 下午好
■ 晚上
. 晚上好
. 晚安。
. 再见。
===========================
2.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>
7.3 Builder 模式中的登场角色
Builder 模式中有以下登场角色。
- Builer (建造者)
负责定义用于生成实例的接口(API)。Builder 角色中准备了用户生成实例的方法。 - ConcreteBuilder (具体的建造者)
负责实现 Builder 角色的接口的类(API)。定义了在生成实例的实际中被调用的方法 - Director (监工)
Director 角色负责使用 Builder 角色的接口(API)来生成实例。并不依赖于 ConcreteBuilder 角色。它只调用在 Builder 角色中被定义的方法。 - Client(使用者)
使用了 Builder 模式(Builder 模式并不包含该角色)。
7.4 拓展思路的要点
|| 谁知道什么
在面向对象的编程中,“谁知道什么” 是非常重要的。我们在编程时,需要注意到哪个类可以使用哪个方法以及使用这个方法到底好不好。
Main 类并不知道调用 Builder 类,它只调用了 Direct 类的 construcet 方法。
另一方面,Director 类知道 Builder 类,它调用 Builder 来的方法来编写文档。但是它并不知道 “真正” 使用的是哪个类。
“只有不知道子类才能替换”,正是因为可以替换,组件才具有高价值。
Director 决定了 Builer 角色中方法的调用顺序,而在 Template Method 模式中,父类决定了子类方法的调用顺序。