组合模式

[TOC]

原理

示例

绘图(C#实现)

旧版实现

用绘图这个例子来说明Composite模式,通过一些基本图像元素(直线、圆等)以及一些复合图像元素(由基本图像元素组合而成)构建复杂的图形树。
在设计中我们对每一个对象都配备一个Draw()方法,在调用时,会显示相关的图形。
可以看到,这里复合图像元素它在充当对象的同时,又是那些基本图像元素的一个容器。
先看一下基本的类结构图:

图中橙色的区域表示的是复合图像元素

示意性代码:

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
public abstract class Graphics
{
protected string Name;

protected Graphics(string name)
{
this.Name = name;
}
public abstract void Draw();
}

public class Picture : Graphics
{
public Picture(string name) : base(name)
{
}

public override void Draw()
{
//
}

public ArrayList GetChilds()
{
//返回所有的子对象
return null;
}
}

而其他作为树枝构件,实现代码如下:

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
public class Line : Graphics
{
public Line(string name) : base(name)
{ }

public override void Draw()
{
Console.WriteLine($"Draw a{Name}");
}
}

public class Circle : Graphics
{
public Circle(string name) : base(name)
{ }

public override void Draw()
{
Console.WriteLine($"Draw a{Name}");
}
}

public class Rectangle : Graphics
{
public Rectangle(string name) : base(name)
{ }

public override void Draw()
{
Console.WriteLine($"Draw a{Name}");
}
}

引入组合模式

现在我们要对该图像元素进行处理:在客户端程序中,需要判断返回对象的具体类型到底是基本图像元素,还是复合图像元素。如果是复合图像元素,我们将要用递归去处理,然而这种处理的结果却增加了客户端程序与复杂图像元素内部结构之间的依赖。那么我们如何去解耦这种关系呢?

我们希望的是客户程序可以像处理基本图像元素一样来处理复合图像元素,这就要引入Composite模式了,需要把对于子对象的管理工作交给复合图像元素,为了进行子对象的管理,它必须提供必要的Add(),Remove()等方法,类结构图如下:

代码如下:

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
public abstract class Graphics
{
protected string Name;

protected Graphics(string name)
{
this.Name = name;
}
public abstract void Draw();

public abstract void Add(Graphics g);
public abstract void Remove(Graphics g);
}

public class Picture : Graphics
{
protected ArrayList PicList = new ArrayList();

public Picture(string name) : base(name)
{ }
public override void Draw()
{
Console.WriteLine($"Draw a{Name}");

foreach (Graphics g in PicList)
{
g.Draw();
}
}

public override void Add(Graphics g)
{
PicList.Add(g);
}
public override void Remove(Graphics g)
{
PicList.Remove(g);
}
}

public class Line : Graphics
{
public Line(string name) : base(name)
{ }

public override void Draw()
{
Console.WriteLine($"Draw a{Name}");
}
public override void Add(Graphics g)
{ }
public override void Remove(Graphics g)
{ }
}

public class Circle : Graphics
{
public Circle(string name) : base(name)
{ }

public override void Draw()
{
Console.WriteLine($"Draw a{Name}");
}
public override void Add(Graphics g)
{ }
public override void Remove(Graphics g)
{ }
}

public class Rectangle : Graphics
{
public Rectangle(string name) : base(name)
{ }

public override void Draw()
{
Console.WriteLine($"Draw a{Name}");
}
public override void Add(Graphics g)
{ }
public override void Remove(Graphics g)
{ }
}

这样引入Composite模式后,客户端程序不再依赖于复合图像元素的内部实现了。

然而,我们程序中仍然存在着问题,因为Line,Rectangle,Circle已经没有了子对象,它是一个基本图像元素,因此Add(),Remove()的方法对于它来说没有任何意义,而且把这种错误不会在编译的时候报错,把错误放在了运行期。

我们希望能够捕获到这类错误,并加以处理,稍微改进一下我们的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Line : Graphics
{
public Line(string name) : base(name)
{ }

public override void Draw()
{
Console.WriteLine($"Draw a{Name}");
}

public override void Add(Graphics g)
{
//抛出一个我们自定义的异常
}

public override void Remove(Graphics g)
{
//抛出一个我们自定义的异常
}
}

这样改进以后,我们可以捕获可能出现的错误,做进一步的处理。

上面的这种实现方法属于透明式的Composite模式

安全的组合模式

如果我们想要更安全的一种做法,就需要把管理子对象的方法声明在树枝构件Picture类里面,这样如果叶子节点Line,Rectangle,Circle使用这些方法时,在编译期就会出错,看一下类结构图:

代码如下:

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
public abstract class Graphics
{
protected string Name;

protected Graphics(string name)
{
Name = name;
}
public abstract void Draw();
}

public class Picture : Graphics
{
protected ArrayList PicList = new ArrayList();

public Picture(string name) : base(name)
{ }
public override void Draw()
{
Console.WriteLine($"Draw a{Name}");

foreach (Graphics g in PicList)
{
g.Draw();
}
}

public void Add(Graphics g)
{
PicList.Add(g);
}
public void Remove(Graphics g)
{
PicList.Remove(g);
}
}

public class Line : Graphics
{
public Line(string name) : base(name)
{ }

public override void Draw()
{
Console.WriteLine($"Draw a{Name}");
}
}

public class Circle : Graphics
{
public Circle(string name)
: base(name)
{ }

public override void Draw()
{
Console.WriteLine($"Draw a{Name}");
}
}

public class Rectangle : Graphics
{
public Rectangle(string name) : base(name)
{ }

public override void Draw()
{
Console.WriteLine($"Draw a{Name}");
}
}

这种方式属于安全式的Composite模式

在这种方式下,虽然避免了前面所讨论的错误,但是它也使得叶子节点和树枝构件具有不一样的接口。

这种方式和透明式的Composite各有优劣,具体使用哪一个,需要根据问题的实际情况而定。

通过Composite模式,客户程序在调用Draw()的时候不用再去判断复杂图像元素中的子对象到底是基本图像元素,还是复杂图像元素,看一下简单的客户端调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Program
{
static void Main(string[] args)
{
Picture root = new Picture("Root");

root.Add(new Line("Line"));
root.Add(new Circle("Circle"));
root.Add(new Rectangle("Rectangle"));

root.Draw();
Console.ReadLine();
}
}

组装电脑(python实现)

只要你对硬件稍微有一些了解,或者打开过机箱换过组件,一定知道 CPU、内存、显卡是插在主板上的,而硬盘也是连在主板上的,在机箱的后面有一排的插口,可以连接鼠标、键盘、耳麦、摄像头等外接配件,而显示器需要单独插电源才能工作。我们可以用代码来模拟台式电脑的组成,这里假设每一个组件都有开始工作和结束工作两个功能,还可以显示自己的信息和组成结构。

代码如下:

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
class Component:
"组件,所有子配件的基类"

def __init__(self, name):
self._name = name

def showInfo(self, indent=""):
pass

def isComposite(self):
return False

def startup(self, indent=""):
print(indent + self._name + " 准备开始工作...")

def shutdown(self, indent=""):
print(indent + self._name + " 即将结束工作...")


class CPU(Component):
"中央处理器"

def __init__(self, name):
super().__init__(name)

def showInfo(self, indent):
print(indent, end="")
print("CPU:" + self._name + ",可以进行高速计算。")


class MemoryCard(Component):
"内存条"

def __init__(self, name):
super().__init__(name)

def showInfo(self, indent):
print(indent, end="")
print("内存:" + self._name + ",可以缓存数据,读写速度快。")


class HardDisk(Component):
"硬盘"

def __init__(self, name):
super().__init__(name)

def showInfo(self, indent):
print(indent, end="")
print("硬盘:" + self._name + ",可以永久存储数据,容量大。")


class GraphicsCard(Component):
"显卡"

def __init__(self, name):
super().__init__(name)

def showInfo(self, indent):
print(indent, end="")
print("显卡:" + self._name + ",可以高速计算和处理图形图像。")


class Battery(Component):
"电源"

def __init__(self, name):
super().__init__(name)

def showInfo(self, indent):
print(indent, end="")
print("电源:" + self._name + ",可以持续给主板和外接配件供电。")


class Fan(Component):
"风扇"

def __init__(self, name):
super().__init__(name)

def showInfo(self, indent):
print(indent, end="")
print("风扇:" + self._name + ",辅助CPU散热。")


class Displayer(Component):
"显示器"

def __init__(self, name):
super().__init__(name)

def showInfo(self, indent):
print(indent, end="")
print("显示器:" + self._name + ",负责内容的显示。")


class Composite(Component):
"配件组合器"

def __init__(self, name):
super().__init__(name)
self._components = []

def showInfo(self, indent):
print(self._name + ",由以下部件组成:")
indent += "\t"
for element in self._components:
element.showInfo(indent)

def isComposite(self):
return True

def addComponent(self, component):
self._components.append(component)

def removeComponent(self, component):
self._components.remove(component)

def startup(self, indent):
super().startup(indent)
indent += "\t"
for element in self._components:
element.startup(indent)

def shutdown(self, indent):
super().startup(indent)
indent += "\t"
for element in self._components:
element.shutdown(indent)


class Mainboard(Composite):
"主板"

def __init__(self, name):
super().__init__(name)

def showInfo(self, indent):
print(indent + "主板:", end="")
super().showInfo(indent)


class ComputerCase(Composite):
"机箱"

def __init__(self, name):
super().__init__(name)

def showInfo(self, indent):
print(indent + "机箱:", end="")
super().showInfo(indent)


class Computer(Composite):
"电脑"

def __init__(self, name):
super().__init__(name)

def showInfo(self, indent):
print(indent + "电脑:", end="")
super().showInfo(indent)

测试代码:

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
def testComputer():
cpu = CPU("Intel Core i5-6600K")
memoryCard = MemoryCard("Kingston Fury DDR4")
hardDisk = HardDisk("Kingston V300 ")
graphicsCard = GraphicsCard("Colorful iGame750")
mainBoard = Mainboard("GIGABYTE Z170M M-ATX")
mainBoard.addComponent(cpu)
mainBoard.addComponent(memoryCard)
mainBoard.addComponent(hardDisk)
mainBoard.addComponent(graphicsCard)

battery = Battery("Antec VP 450P")
fan = Fan("DEEPCOOL 120T")
computerCase = ComputerCase("SAMA MATX")
computerCase.addComponent(battery)
computerCase.addComponent(mainBoard)
computerCase.addComponent(fan)

displayer = Displayer("AOC LV243XIP")

computer = Computer("Tony DIY电脑")
computer.addComponent(displayer)
computer.addComponent(computerCase)

computer.showInfo("")
print("\n开机过程:")
computer.startup("")
print("\n关机过程:")
computer.shutdown("")

testComputer()

输出结果:

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
电脑:Tony DIY电脑,由以下部件组成:
显示器:AOC LV243XIP,负责内容的显示。
机箱:SAMA MATX,由以下部件组成:
电源:Antec VP 450P,可以持续给主板和外接配件供电。
主板:GIGABYTE Z170M M-ATX,由以下部件组成:
CPU:Intel Core i5-6600K,可以进行高速计算。
内存:Kingston Fury DDR4,可以缓存数据,读写速度快。
硬盘:Kingston V300 ,可以永久存储数据,容量大。
显卡:Colorful iGame750,可以高速计算和处理图形图像。
风扇:DEEPCOOL 120T,辅助CPU散热。

开机过程:
Tony DIY电脑 准备开始工作...
AOC LV243XIP 准备开始工作...
SAMA MATX 准备开始工作...
Antec VP 450P 准备开始工作...
GIGABYTE Z170M M-ATX 准备开始工作...
Intel Core i5-6600K 准备开始工作...
Kingston Fury DDR4 准备开始工作...
Kingston V300 准备开始工作...
Colorful iGame750 准备开始工作...
DEEPCOOL 120T 准备开始工作...

关机过程:
Tony DIY电脑 准备开始工作...
AOC LV243XIP 即将结束工作...
SAMA MATX 准备开始工作...
Antec VP 450P 即将结束工作...
GIGABYTE Z170M M-ATX 准备开始工作...
Intel Core i5-6600K 即将结束工作...
Kingston Fury DDR4 即将结束工作...
Kingston V300 即将结束工作...
Colorful iGame750 即将结束工作...
DEEPCOOL 120T 即将结束工作...

类图如下: