初探生成器模式

代码链接

koonchen/design-patterns/builder

要点

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

现在有一个 RTF 文档阅读器我们叫他 RTFReader ,他可以将阅读内容转换成多种正文格式,比如 ASCII 或一个正文窗口组件等。我们在完成这一转换时,有一个问题是转换的数量是无限的,我们需要实现新的转换的增加的同时,不改变 RTFReader 本身。

生成器模式的解法是使用一个统一的 TextConvert 接口完成转换,当 RTFReader 读出一个 RTF 标记,就给他进行转换,而具体的转换方法又是 TextConvert 接口的具体实现,因此可以在不改变 RTFReader 的实现的同时,非常便捷地完成转换工作。

上面的模式实现中, RTFReader 我们称之为 Director 导向器, TextConvert 的每一个具体实现都称之为 Builder 生成器。因此,生成器模式的目的就是将对象的处理和对象的组成进行分离。下图展示了上例:

cover

Builder 模式的使用情况:

  • 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
  • 当构造过程必须允许被构造的对象有不同的表示时。

他拥有以下参与者:

  • Builder—为创建一个 Product 对象的各个部件指定抽象接口。
  • ConcreteBuilder—实现接口以构造和装配产品的各个部件。还需要有一个检查产品的接口,一般是 Get 方法,验证生成对象。
  • Director—构造一个使用 Builder 接口的对象。
  • Product—表示被构造的对象。

模式结构也就清晰了:

cover

因为用了接口方法,所以聚合关系是必须的。在协作中,产品需要早于工厂,生成器早于导向器,接着用 Builder 创建各个部分,最后检查产品,结束。

  • 改变一个产品的内部表示。 Builder 相当于产品的构造,现在被分离了,定义一个新的生成方法非常简单,改变内部表示就是这个意思。
  • 分离构造和表示。不同的 Director 可以复用不同的 Builder ,而 Director 和 Builder 又互不相知。
  • 构造过程进行更精细的控制。因为导向器的存在,我们可以一步一步构造产品,比如每次创建一个部分,所以 Builder 接口也就比其他创建型模式更好反映了产品的构造过程。

实现

生成器先于导向器完成,默认情况下什么都不做,只在具体的 ConcreteBuilder 中进行实现,而这个过程中有三个小问题:

  • 装配和构造接口:构造尽量简单通用,Builder 装配需要满足各种具体生成器,因此突出两个字,简单
  • 为什么产品没有抽象类:以上面的 RTF 为例,ASCII 格式和其他文本对象之间可能千差万别,同时现在我们处于一个知道每一步装配什么的位置,产品公共接口的存在并不必须也不重要也不大可能
  • Builder 缺省方法:接口没有具体实现当然是不存在的,这里的意思是有一个默认的空方法, C++ 中也就是非纯虚函数, java 就用抽象类实现。

java 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class Product {
private String describe;
private String name;

void withNameDes(String name, String describe) {
this.name = name;
this.describe = describe;
}

@Override
public String toString() {
return "Product{" +
"describe='" + describe + '\'' +
", name='" + name + '\'' +
'}';
}
}

abstract class Builder {
Product product = null;
abstract void buildPart();
Product getResult(){
System.out.println(product);
return product;
}
}

class ConcreteBuilder extends Builder {
@Override
void buildPart() {
product = new Product();
product.withNameDes("product", "Product instance has been created");
}
}

class Director {
Builder builder;
Director(Builder builder) {
this.builder = builder;
}

void construct() {
builder.buildPart();
}
}

public class BuilderBasic {
public static void main(String[] args) {
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
director.construct();
builder.getResult();
}
}

我们发现,实际上产品是被导向器使用接口所创建的,我们需要获得的是 product ,创建了 builder 来进行 product 的构建,使用 director 来进行使用,思考发现, director 的职能就是使用,因此进行一些改进:

  • 其一,不直观创建出 director ,而是将笔者角度来假设存在,这样我们的 main 或者实际调用处就作为 director
  • 其二,我们所有的信息来源可以发现都是上面的 builder ,为什么我们的 product 存在的属性是这些,就是来自 builder ,因此完全可以对 builder 进行一定的扩展,将属性赋值给 builder ,而创建 product 的时候,直接使用 builder 对象,这样似乎更为直观。

java 改进

根据上面的两点总结进行了改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class ProductAdv {
private String describe;
private String name;
ProductAdv(ConcreteBuilderAdv builder) {
this.describe = builder.getName();
this.name = builder.getDescribe();
}

@Override
public String toString() {
return "ProductAdv{" +
"describe='" + describe + '\'' +
", name='" + name + '\'' +
'}';
}
}

class ConcreteBuilderAdv {
private String describe;
private String name;
ConcreteBuilderAdv withName(String name) {
this.name = name;
return this;
}

ConcreteBuilderAdv withDes(String describe) {
this.describe = describe;
return this;
}

public String getName() {
return name;
}

public String getDescribe() {
return describe;
}

ProductAdv getResult() {
return new ProductAdv(this);
}
}

public class BuilderAdv {
public static void main(String[] args) {
ProductAdv product = new ConcreteBuilderAdv()
.withName("productAdv")
.withDes("ProductAdv instance has been created")
.getResult();
System.out.println(product);
}
}

上面就是 java 实现的生成器模式。

python 实现

这里直接省略 director 的创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/env python
# coding:utf8

class Product(object):
def withNameDes(self, **kwargs):
self._name = kwargs.get('name')
self._des = kwargs.get('describe')

def __repr__(self, *args, **kwargs):
return 'name: '+self._name+'\ndescribe: '+self._des

class Builder(object):
def buildePart(self):
pass
def getResult(self):
pass

class ConcreteBuilder(Builder):
def buildePart(self):
self._product = Product()
self._product.withNameDes(name='product',
describe='Product instance has been created')
def getResult(self):
print(self._product)
return self._product

if __name__ == '__main__':
builder = ConcreteBuilder()
builder.buildePart()
builder.getResult()

python 改进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/env python
# coding:utf8

class Product(object):
def __init__(self, builder):
self._name = builder._name
self._describe = builder._describe

def __repr__(self, *args, **kwargs):
return 'name: '+self._name+'\ndescribe: '+self._describe

class ConcreteBuilder(object):
def withName(self, name):
self._name = name
return self
def withDescribe(self, describe):
self._describe = describe
return self
def getsult(self):
return Product(self)

if __name__ == '__main__':
product = ConcreteBuilder()\
.withName('product')\
.withDescribe('Product instance has been created')\
.getsult()
print(product)

改进方式也是将 builder 封装而作为了参数。

我们常常拿抽象方法和生成器进行对比,或者将工厂和生成器进行对比,因为他们都可以完成复杂对象的创建,但是在 abstract factory 中,我们的目的是创建多个系列,而 builder 中是创建一种复杂对象且着重于一步步的构造。