本文来自廖雪峰,郎涯进行简单排版与补充
生成器模式即Builder,是为了创建一个复杂的对象,需要多个步骤完成创建,或者需要多个零件组装的场景,且创建过程中可以灵活调用不同的步骤或组件。
我们仍然以Markdown转HTML为例,因为直接编写一个完整的转换器比较困难,但如果针对类似下面的一行文本:
# this is a heading
转换成HTML就很简单:
<h1>this is a heading</h1>
因此,我们把Markdown转HTML看作一行一行的转换,每一行根据语法,使用不同的转换器:
- 如果以
#
开头,使用HeadingBuilder
转换; - 如果以
>
开头,使用QuoteBuilder
转换; - 如果以
---
开头,使用HrBuilder
转换; - 其余使用
ParagraphBuilder
转换。
这个HtmlBuilder
写出来如下:
public class HtmlBuilder {
private HeadingBuilder headingBuilder = new HeadingBuilder();
private HrBuilder hrBuilder = new HrBuilder();
private ParagraphBuilder paragraphBuilder = new ParagraphBuilder();
private QuoteBuilder quoteBuilder = new QuoteBuilder();
public String toHtml(String markdown) {
StringBuilder buffer = new StringBuilder();
markdown.lines().forEach(line -> {
if (line.startsWith("#")) {
buffer.append(headingBuilder.buildHeading(line)).append('\n');
} else if (line.startsWith(">")) {
buffer.append(quoteBuilder.buildQuote(line)).append('\n');
} else if (line.startsWith("---")) {
buffer.append(hrBuilder.buildHr(line)).append('\n');
} else {
buffer.append(paragraphBuilder.buildParagraph(line)).append('\n');
}
});
return buffer.toString();
}
}
注意观察上述代码,HtmlBuilder
并不是一次性把整个Markdown转换为HTML,而是一行一行转换,并且,它自己并不会将某一行转换为特定的HTML,而是根据特性把每一行都“委托”给一个XxxBuilder
去转换,最后,把所有转换的结果组合起来,返回给客户端。
这样一来,我们只需要针对每一种类型编写不同的Builder。例如,针对以#
开头的行,需要HeadingBuilder
:
public class HeadingBuilder {
public String buildHeading(String line) {
int n = 0;
while (line.charAt(0) == '#') {
n++;
line = line.substring(1);
}
return String.format("<h%d>%s</h%d>", n, line.strip(), n);
}
}
使用Builder模式时,适用于创建的对象比较复杂,最好一步一步创建出“零件”,最后再装配起来。
由于我们经常需要构造URL字符串,可以使用Builder模式编写一个URLBuilder,调用方式如下:
String url = URLBuilder.builder() // 创建Builder
.setDomain("www.liaoxuefeng.com") // 设置domain
.setScheme("https") // 设置scheme
.setPath("/") // 设置路径
.setQuery(Map.of("a", "123", "q", "K&R")) // 设置query
.build(); // 完成build