桥接模式
桥接模式是软件设计模式中最复杂的模式之一,它把事物对象和其具体行为、具体特征分离开来,使它们可以各自独立的变化。事物对象仅是一个抽象的概念。如"圆形"、"三角形"归于抽象的"形状"之下,而"画圆"、"画三角"归于实现行为的"画图"类之下,然后由"形状"调用"画图"。
引自维基百科
按照维基百科的描述,绘制UML如下
UML图解析
抽象类和接口是聚合的关系,即调用和被调用的关系。如此一来搭好桥后,具体实现类调用方法=>父类抽象类的方法=>行为接口方法=>具体接口行为实现类,以完成连接,同时两者又相互独立易扩展:
从上图可以很明显的看到Shape类就相当于桥,通过它让接口和本身子类产生了关联
优点
分离抽象接口及其实现部分,使得抽象和实现可以沿着各自的维度来变化
桥接模式有时类似于多继承方案。但是多继承方案违背了类的单一职责原则,复用性比较差,而且多继承结构中累的个数非常庞
桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原系统
缺点:
桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
# 实战案例
需求:现在有3个英雄,每个英雄可以任意搭配2种种召唤师技能
常见的解法:每个英雄都配备2种召唤师技能,如果需要增加英雄,每增加一个英雄类相应增加2个召唤师技能类,如果需要增加技能,需要修改每一个英雄类和在技能族中添加新技能。很明显不应该使用这种方式,一旦增加技能那将是地狱级难度
桥接模式:技能和英雄是互相独立变化的两个维度,通过关联方式产生联系,无论英雄类还是召唤师技能类增加都不会互相影响
类图如下
# 代码实现
客户端调用代码
program BridgePattern;
{$APPTYPE CONSOLE}
{$R *.res}
{桥接模式}
uses
System.SysUtils,
BridgeUnit in 'BridgeUnit.pas';
var
List: TSkill;
begin
try
//构造英雄
var SindlaHero := TSindlaHero.Create();
//创建技能列表,相当于装配技能
List[0] := TFastRun.create();
List[1] := TLgnition.Create();
SindlaHero.SkillList := List;
//辛德拉使用技能-引燃
SindlaHero.UseSkill(Lgnition);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
readln;
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
核心单元
unit BridgeUnit;
interface
type
ISkill = interface
//技能接口
['{70A22670-D265-4CE2-9367-881BBA434D3A}']
//技能功效,可以作为技能的具体实现方式
procedure skillEffect(HeroName: string);
end;
TFastRun = class(TInterfacedObject, ISkill)
//疾跑
public
procedure skillEffect(HeroName: string); overload;
end;
TLgnition = class(TInterfacedObject, ISkill)
//引燃
public
procedure skillEffect(HeroName: string); overload;
end;
type
//技能列表
TSkill = array[0..1] of ISkill;
//技能选项
TSkillEnum = (Lgnition, FastRun);
THero = class abstract
//英雄类
private
FSkillList: TSkill;
public
property SkillList: TSkill read FSkillList write FSkillList;
procedure UseSkill(SkillEnum: TSkillEnum); virtual; abstract;
end;
TGoldenHero = class(THero)
//金克丝
public
procedure UseSkill(SkillEnum: TSkillEnum); override;
end;
TSindlaHero = class(THero)
//辛德拉
public
procedure UseSkill(SkillEnum: TSkillEnum); override;
end;
implementation
{ TFastRun }
procedure TFastRun.skillEffect(HeroName: string);
begin
Writeln('召唤师【' + HeroName + '】开启疾跑,移速增加300%');
end;
{ TLgnition }
procedure TLgnition.skillEffect(HeroName: string);
begin
Writeln('召唤师【' + HeroName + '】开启引燃,对目标增加10%的持续伤害,持续时间6秒');
end;
{ TGoldenHero }
procedure TGoldenHero.UseSkill(SkillEnum: TSkillEnum);
begin
case SkillEnum of
Lgnition:
begin
SkillList[0].skillEffect('金克丝');
end;
FastRun:
begin
SkillList[1].skillEffect('金克丝');
end;
end;
end;
{ TSindlaHero }
procedure TSindlaHero.UseSkill(SkillEnum: TSkillEnum);
begin
case SkillEnum of
Lgnition:
begin
SkillList[0].skillEffect('辛德拉');
end;
FastRun:
begin
SkillList[1].skillEffect('辛德拉');
end;
end;
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
运行结果
# 结语
第一次尝试使用Delphi中的枚举类型,各位不要笑话。还有英雄联盟中的英雄人物的英文名实在不知道怎么写,用翻译软件直接翻译的。。。