建造者模式(Builder)

First Post:

Last Update:

建造者模式(Builder)

引言:根据依赖倒置原则,抽象不应该依赖于细节,细节应该依赖于抽象。将工作流程看作抽象的流程,具体怎么做,各个细节都依赖于这个抽象。

为了不让程序“缺胳膊少腿”,最好的办法是规定。

建造的过程是稳定的,而具体建造的什么东西、各个细节是不同的,但对于用户来说,用户不管这些内容,他只知道他想要得到什么,我们只管建造好对应的东西给用户就行。

如果需要将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。我们可以应用——“建造者模式”,又叫生成器模式(Generator)。

建造者模式将一个产品的内部表象与产品的生成过程分割开来,从而使一个建造过程生成具有不同的内部表象的产品对象。用了建造者模式,只需要指定需要建造的类型就可以得到它们,而具体的建造过程和细节就不必知道了

建造者模式结构图

建造者模式成员分析

Director:控制者类,这是控制整个组合过程,在这个类内部有个Construct()方法,这个方法的作用就是通过调用Builder内部的各个组件的生成方法来完成组装;

Builder:构建者接口,定义各部件生成的方法;

ConcreteBuilder:具体构建者类:实现Builder构建者接口,具体定义如何生成各个部件;依赖于Product成品类,其中还有获取成品组装结构的方法GetResult()方法;

Product:成品类

建造者模式代码结构

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//产品类
public class Product {
IList<string> parts = new IList<string>();

public void add(String part)
{//添加产品部件
parts.Add(part);
}

public void Show() {
Console.WriteLine("产品创建——");
for (String part in parts) {
//show parts
}
}
}

//builder类——抽象建造者类,确定产品由两个部件组成并声明返回构建结果的方法。
abstract class Builder
{
public abstract void BuildPartA();
public abstract void BuildPartB();
public abstract Product GetBuildResult();
}

//具体建造者类
class ConcreteBuilder1 : Builder
{
private Product product = new Product();
//建造具体的两个部件
public Override void BuilderPartA()
{
product.Add("部件A");
}
public Override void BuilderPartB()
{
product.Add("部件B");
}
public Override void GetBuildResult()
{
return product;
}
}


class ConcreteBuilder2 : Builder
{
private Product product = new Product();
//建造具体的两个部件
public Override void BuilderPartA()
{
product.Add("部件X");
}
public Override void BuilderPartB()
{
product.Add("部件Y");
}
public Override void GetBuildResult()
{
return product;
}
}

Director类——指挥者类(中间类),用于指挥建造过程
class Director
{
public void Construct(Builder builder)
{
builder.BuildPartA();
builder.BuildPartB();
builder.GetBuildResult();
}
}

客户端代码,客户不需要知道具体的构造过程
Director director = new Director( );
Builder b1 = new ConcreteBuilder1( );
Builder b2 = new ConcreteBuilder2( );

director.Construct(b1);
Product p1 = b1.GetResult( );
p1.Show( );

director.Construct(b2);
Product p2 = b2.GetResult( );
p2.Show( );

建造者模式总结

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

什么时候使用构造者模式呢?

主要用于创建一些复杂的对象,这些对象内部构造间的顺序是稳定的,但对象内部的构造通常面临着复杂的变化时。
构造者模式的好处就是使得建造代码和表示代码分离,由于建造者模式隐藏了该产品是如何组装的,所以如果需要改变一个产品的内部表示,需要新建一个具体构造类来继承抽象构造类并实现。

建造者模式是怎么做的呢?

通过抽象类来做产品的规范
通过继承抽象类来实现不同产品的各个不同的构造细节
通过中间类(指挥者类)来控制一致的构造过程
客户不需要知道构建的细节,只需要了解两个接口——中间类(指挥者)和建造具体产品类。

建造者模式应用场景

如果我们需要将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示的意图时,我们可以使用 Builder模式,又叫生成器模式。如果我们用了Builder模式,那么用户就只需要指定需要建造的类型就可以得到它们,而具体建造的过程和细节就不需要知道了。
比如现在我们有一个这样的使用场景,需要在屏幕上画小人,人要有头手脚,要画不同的人,胖的小人,瘦的小人,矮的小人。按照通常的写法,会有很多的样板代码,画人的头,画人脚手,如果一不小心,非常容易缺胳膊少腿。

实例1——建造复杂对象

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class Person {
//限于篇幅get和set方法此处省略
Head head;
Body body;
Arm leftArm;
Arm rightArm;
Leg leftLeg;
Leg rightLeg;

public void drawHead(int size){...}
public void drawBody(int size){...}
public void drawLeftArm(int size){...}
public void drawRightArm(int size){...}
public void drawLeftLeg(int size){...}
public void drawRightLeg(int size){...}
}

abstract class BuilderPerson {
protected Person person = new Person();
public abstract void buildHead();
public abstract void buildBody();
public abstract void buildLeftArm();
public abstract void buildRightArm();
public abstract void buildLeftLeg();
public abstract void buildRightLeg();
}

public class BuilderThinPerson extends BuilderPerson{
@Override
public void buildHead() {
person.drawHead(10);
}
@Override
public void buildBody() {
person.drawBody(10);
//画胖小人只需将这边的数值修改,
//再生成一个类即可
}
@Override
public void buildLeftArm() {
person.drawLeftArm(5);
}
@Override
public void buildRightArm() {
person.drawRightArm(5);
}
@Override
public void buildLeftLeg() {
person.drawLeftLeg(7);
}
@Override
public void buildRightLeg() {
person.drawRightLeg(7);
}
}

我们还缺Builder模式中一个非常重要的类,指挥者(Director),用它来控制建造过程,也用来隔离用户与建造过程的关联。
public class PersonDirector{
private BuilderPerson pb;
public PersonDirector(BuilderPerson pb){
this.pb = pb;
}
//建造的过程在指挥者这里完成,用户就不需要知道了
public void createPerson() {
pb.buildHead();
pb.buildBody();
pb.buildLeftArm();
pb.buildRightArm();
pb.buildLeftLeg();
pb.buildRightLeg();
}
}

客户端代码
BuilderPerson bp = new BuilderThinPerson();
PersonDirector pd = new PersonDirector(bp);
pd.createPerson();

实例2——复杂构造(大量可选参数拓展)

遇到多个构造器参数时要考虑用构建器。静态工厂和构造器有个共同的局限性:它们都不能很好地扩展到大量的可选参数。
考虑这样的一个场景:用一个类表示包装食品外面显示的营养成分标签。这些标签中有几个域是必需的:每份的含量、每罐的含量以及每份的卡路里,还有超过20个可选域:总脂肪量、饱和脂肪量、转化脂肪、胆固醇、钠等等。
程序员一向习惯采用重叠构造器模式,在这种模式下,你提供第一个只有必要参数的构造器,第二个构造器有一个可选参数,第三个有两个可选参数,以此类推,最后一个构造器包含所有可选参数。重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然难以阅读。一长串类型相同的参数会导致一些微妙的错误。如果客户端不小心颠倒了其中两个参数的顺序,编译器也不会出错,但是程序在运行时会出现错误的行为。

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
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;

public static class Builder {
//required parameters 必须的参数表列
private final int servingSize;
private final int servings;

//Optional parameters - initialized to default values
//可选的参数表列,初始化到默认值
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
public Builder(int servingSize, int servings){
this.servingSize = servingSize;
this.servings = servings
}
public Builder calories(int val){
calories = val;
return this;
}
public Builder fat(int val){
fat = val;
return this;
}
public Builder sodium(int val) {
sodium = val;
return this;
}
public Builder carbohydrate(int val){
carbohydrate = val;
return this;
}
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder){
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}

建造者模式优点

  • 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在导演类中对整体而言可以取得比较好的稳定性。
  • 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
  • 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
  • 其次,建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。