建造者模式
原文:The intent of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations.
译文:将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示
很明显翻译之后还是不懂是什么意思,简单的说就是当一个类的对象创建的过程足够复杂的时候我们需要将它的创建过程和表示进行分离。从而提升我们代码的复用性
我在网络上看到很多人以组装电脑为例子,那我们也以此为例。我们最终的目的是组装两个不同品牌的电脑苹果和联想的Thinkpad。我们知道组装电脑需要很多零部件,但是这些零部件有必须品和非必须品,于是类的定义如下
type
TComputer = class
private
// 必须 CUP
FCpu: string;
// 必须 内存
FRam: string;
// 可选 USB数量
FUsbCount: Integer;
// 可选 键盘
FKeyboard: string;
// 可选 显示器
FDisplay: string;
public
end;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
那么针对这个类的对象该如何创建呢,通常情况下会有两种处理方法,构造方法和属性字段
构造方法
constructor TComputer.Create(Cpu, Ram: string; UsbCount: Integer;
Keyboard, Display: string);
begin
self.FCpu := Cpu;
self.FRam := Ram;
// 可选参数
self.FUsbCount := UsbCount;
self.FKeyboard := Keyboard;
self.FDisplay := Display;
end;
constructor TComputer.Create(Cpu, Ram: string);
begin
self.FCpu := Cpu;
self.FRam := Ram;
// 可选参数
self.FUsbCount := 2;
self.FKeyboard := '蝰蛇';
self.FDisplay := '三星';
end;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
属性
property Cpu: string read FCpu write FCpu;
property Ram: string read FRam write FRam;
property UsbCount: Integer read FUsbCount write FUsbCount;
property Keyboard: string read FKeyboard write FKeyboard;
property Display: string read FDisplay write FDisplay;
2
3
4
5
6
7
8
9
有的朋友可能认为属性都多余直接访问字段即可,你可以去翻翻Delphi官方的代码很明显不推荐这种玩法。
问题
第一种主要是使用及阅读不方便,你可以想象一下如果一个函数的参数有10个那是什么感觉
第二种方式在构建过程中对象的状态容易发生变化,造成错误。因为那个类中的属性是分步设置的,所以就容易出错
应用场景:当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。
# Builder模式
在TComputer 中创建一个内部类 TBuilder,然后将TComputer 中的参数都复制到Builder类中。
在TComputer中创建一个private的构造函数,参数为TBuilder类型
在TBuilder中创建一个public的构造函数,参数为TComputer中必填的那些参数,cpu 和ram。
在TBuilder中创建设置函数,对TComputer中那些可选参数进行赋值,返回值为TBuilder类型的实例
在TBuilder中创建一个build()方法,在其中构建TComputer的实例并返回
核心代码
PS:完整代码太长了
interface
type
TComputer = class
private
// 必须 CUP
FCpu: string;
// 必须 内存
FRam: string;
// 可选 USB数量
FUsbCount: Integer;
// 可选 键盘
FKeyboard: string;
// 可选 显示器
FDisplay: string;
public
type
TBuilder = class
private
// 必须 CUP
FCpu: string;
// ..省略其他字段
public
constructor Create(Cpu: string; Ram: string); overload;
function Build(): TComputer;
function SetUsbCount(UsbCount: Integer): TBuilder;
//省略其他set
property Cpu: string read FCpu;
//省略其他的 property
end;
private
// 空参构造
constructor Create; overload;
// 在构造方法内部
constructor Create(Builder: TBuilder); overload;
end;
implementation
{ TComputer }
constructor TComputer.Create;
begin
inherited Create;
end;
{ TComputer.Builder }
function TComputer.TBuilder.Build: TComputer;
begin
Result := TComputer.Create(self);
end;
constructor TComputer.TBuilder.Create(Cpu, Ram: string);
begin
self.FCpu := Cpu;
self.FRam := Ram;
end;
function TComputer.TBuilder.SetDisplay(Display: string): TBuilder;
begin
self.FDisplay := Display;
//注意返回值
Result := self;
end;
//....省略其他的Set
constructor TComputer.Create(Builder: TBuilder);
begin
self.FCpu := Builder.Cpu;
self.FRam := Builder.Ram;
self.FKeyboard := Builder.Keyboard;
self.FDisplay := Builder.Display;
self.FUsbCount := Builder.UsbCount;
end;
end.
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
调用代码
var
MacComputer: TComputer;
begin
MacComputer := TComputer.TBuilder.Create('因特尔', '三星').SetUsbCount(2)
.SetKeyboard('Mac').Build;
Readln;
end.
2
3
4
5
6
7
8
9
这种写法其实是一种比较讨巧的写法,也有人称之为链式调用
# 传统Builder
构建者模式UML图如下所示
builder模式有4个角色
Product: 最终要生成的对象,例如 Computer实例。
Builder: 构建者的抽象基类(有时会使用接口代替)。其定义了构建Product的抽象步骤,其实体类需要实现这些步骤。其会包含一个用来返回最终产品的方法Product getProduct()。
ConcreteBuilder: Builder的实现类。
Director: 决定如何构建最终产品的算法. 其会包含一个负责组装的方法void Construct(Builder builder), 在这个方法中通过调用builder的方法,就可以设置builder,等设置完成后,就可以通过builder的 getProduct() 方法获得最终的产品。
接下来将最开始的例子使用传统方式来实现一遍。
第一步:我们的目标Computer类
TComputer = class
private
// 必须 CUP
FCpu: string;
// 必须 内存
FRam: string;
// 可选 USB数量
FUsbCount: Integer;
// 可选 键盘
FKeyboard: string;
// 可选 显示器
FDisplay: string;
public
property Cpu: string read FCpu write FCpu;
property Ram: string read FRam write FRam;
property UsbCount: Integer read FUsbCount write FUsbCount;
property Keyboard: string read FKeyboard write FKeyboard;
property Display: string read FDisplay write FDisplay;
constructor Create(Cpu: string; Ram: string); overload;
end;
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
第二步:抽象构建者类
TComputerBuilder = class
public
procedure SetUsbCount(); virtual; abstract;
procedure SetKeyboard(); virtual; abstract;
procedure SetDisplay(); virtual; abstract;
function GetComputer(): TComputer; virtual; abstract;
end;
2
3
4
5
6
7
8
9
10
11
12
13
第三步:实体构建者类,我们可以根据要构建的产品种类产生多了实体构建者类,这里我们需要构建两种品牌的电脑,苹果电脑和联想电脑,所以我们生成了两个实体构建者类。
TMacComputerBuilder = class(TComputerBuilder)
private
FComputer: TComputer;
public
constructor Create(Cpu: string; Ram: string); overload;
procedure SetUsbCount(); override;
procedure SetKeyboard(); override;
procedure SetDisplay(); override;
function GetComputer(): TComputer; override;
end;
//方法实现
{ TMacComputerBuilder }
constructor TMacComputerBuilder.Create(Cpu, Ram: string);
begin
// 创建计算机对象,填充必须的参数
FComputer := TComputer.Create(Cpu, Ram);
end;
function TMacComputerBuilder.GetComputer: TComputer;
begin
Result := FComputer;
end;
procedure TMacComputerBuilder.SetDisplay;
begin
FComputer.Display := '苹果显示器';
end;
procedure TMacComputerBuilder.SetKeyboard;
begin
FComputer.Keyboard := '苹果键盘';
end;
procedure TMacComputerBuilder.SetUsbCount;
begin
FComputer.UsbCount := 2;
end;
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
第四步:指导者类(Director)
TComputerDirector = class
procedure MakeComputer(ComputerBuilder: TComputerBuilder);
end;
//方法实现
{ TComputerDirector }
procedure TComputerDirector.MakeComputer(ComputerBuilder: TComputerBuilder);
begin
ComputerBuilder.SetUsbCount();
ComputerBuilder.SetDisplay();
ComputerBuilder.SetKeyboard();
end;
2
3
4
5
6
7
8
9
10
11
12
13
14
使用
var
Director := TComputerDirector.Create(); // 1
var
builder := TMacComputerBuilder.Create('I5处理器', '三星125'); // 2
Director.MakeComputer(builder); // 3
var
MacComputer := builder.GetComputer(); // 4
2
3
4
5
6
7
8
9
案例只构建了苹果没有Thinkpad,但是大同小异
小结:实际编写的时候其实使用第一种方式较多。和工厂模式相比建造者模式是有顺序要求的