動機(Motivate):
組合模式有時候又叫做部分
-
整體模式,它使我們樹型結構的問題中,模糊了簡單元素和復雜元素的概念,客戶程序可以向處理簡單元素一樣來處理復雜元素,從而使得客戶程序與復雜元素的內部結構解耦。
意圖(Intent):
將對象組合成樹形結構以表示“部分
-
整體”的層次結構。
Composite
模式使得用戶對單個對象和組合對象的使用具有一致性。
-----------《設計模式》GOF
結構圖(Struct):

生活中的例子:

適用性:
1.你想表示對象的部分-整體層次結構
2
.你希望用戶忽略組合對象與單個對象的不同,用戶將統一地使用組合結構中的所有對象。
代碼實現:
這里我們用繪圖這個例子來說明
Composite
模式,通過一些基本圖像元素(直線、圓等)以及一些復合圖像元素(由基本圖像元素組合而成)構建復雜的圖形樹。在設計中我們對每一個對象都配備一個
Draw()
方法,在調用時,會顯示相關的圖形。可以看到,這里復合圖像元素它在充當對象的同時,又是那些基本圖像元素的一個容器。先看一下基本的類結構圖:

圖中橙色的區域表示的是復合圖像元素。
示意性代碼:
1
public
abstract
class
Graphics
2
{
3
protected
string
_name;
4
5
public
Graphics(
string
name)
6
{
7
this
._name
=
name;
8
}
9
public
abstract
void
Draw();
10
}
11
12
public
class
Picture : Graphics
13
{
14
public
Picture(
string
name)
15
:
base
(name)
16
{ }
17
public
override
void
Draw()
18
{
19
//
20
}
21
22
public
ArrayList GetChilds()
23
{
24
//
返回所有的子對象
25
}
26
}
而其他作為樹枝構件,實現代碼如下:
1
public
class
Line:Graphics
2
{
3
public
Line(
string
name)
4
:
base
(name)
5
{ }
6
7
public
override
void
Draw()
8
{
9
Console.WriteLine(
"
Draw a
"
+
_name.ToString());
10
}
11
}
12
13
public
class
Circle : Graphics
14
{
15
public
Circle(
string
name)
16
:
base
(name)
17
{ }
18
19
public
override
void
Draw()
20
{
21
Console.WriteLine(
"
Draw a
"
+
_name.ToString());
22
}
23
}
24
25
public
class
Rectangle : Graphics
26
{
27
public
Rectangle(
string
name)
28
:
base
(name)
29
{ }
30
31
public
override
void
Draw()
32
{
33
Console.WriteLine(
"
Draw a
"
+
_name.ToString());
34
}
35
}
現在我們要 對該圖像元素進行處理:在客戶端程序中,需要判斷返回對象的具體類型到底是基本圖像元素,還是復合圖像元素。如果是復合圖像元素,我們將要用遞歸去處理, 然而這種處理的結果卻增加了客戶端程序與復雜圖像元素內部結構之間的依賴,那么我們如何去解耦這種關系呢?我們希望的是客戶程序可以像處理基本圖像元素一 樣來處理復合圖像元素,這就要引入
Composite
模式了,需要把對於子對象的管理工作交給復合圖像元素,為了進行子對象的管理,它必須提供必要的
Add()
,
Remove()
等方法,類結構圖如下:

示意代碼:
1
public
abstract
class
Graphics
2
{
3
protected
string
_name;
4
5
public
Graphics(
string
name)
6
{
7
this
._name
=
name;
8
}
9
public
abstract
void
Draw();
10
public
abstract
void
Add();
11
public
abstract
void
Remove();
12
}
13
14
public
class
Picture : Graphics
15
{
16
protected
ArrayList picList
=
new
ArrayList();
17
18
public
Picture(
string
name)
19
:
base
(name)
20
{ }
21
public
override
void
Draw()
22
{
23
Console.WriteLine(
"
Draw a
"
+
_name.ToString());
24
25
foreach
(Graphics g
in
picList)
26
{
27
g.Draw();
28
}
29
}
30
31
public
override
void
Add(Graphics g)
32
{
33
picList.Add(g);
34
}
35
public
override
void
Remove(Graphics g)
36
{
37
picList.Remove(g);
38
}
39
}
40
41
public
class
Line : Graphics
42
{
43
public
Line(
string
name)
44
:
base
(name)
45
{ }
46
47
public
override
void
Draw()
48
{
49
Console.WriteLine(
"
Draw a
"
+
_name.ToString());
50
}
51
public
override
void
Add(Graphics g)
52
{ }
53
public
override
void
Remove(Graphics g)
54
{ }
55
}
56
57
public
class
Circle : Graphics
58
{
59
public
Circle(
string
name)
60
:
base
(name)
61
{ }
62
63
public
override
void
Draw()
64
{
65
Console.WriteLine(
"
Draw a
"
+
_name.ToString());
66
}
67
public
override
void
Add(Graphics g)
68
{ }
69
public
override
void
Remove(Graphics g)
70
{ }
71
}
72
73
public
class
Rectangle : Graphics
74
{
75
public
Rectangle(
string
name)
76
:
base
(name)
77
{ }
78
79
public
override
void
Draw()
80
{
81
Console.WriteLine(
"
Draw a
"
+
_name.ToString());
82
}
83
public
override
void
Add(Graphics g)
84
{ }
85
public
override
void
Remove(Graphics g)
86
{ }
87
}
這樣引入
Composite
模式后,客戶端程序不再依賴於復合圖像元素的內部實現了。然而,我們程序中仍然存在着問題,因為
Line
,
Rectangle
,
Circle
已經沒有了子對象,它是一個基本圖像元素,因此
Add()
,
Remove()
的方法對於它來說沒有任何意義,而且把這種錯誤不會在編譯的時候報錯,把錯誤放在了運行期,我們希望能夠捕獲到這類錯誤,並加以處理,稍微改進一下我們的程序:
1
public
class
Line : Graphics
2
{
3
public
Line(
string
name)
4
:
base
(name)
5
{ }
6
7
public
override
void
Draw()
8
{
9
Console.WriteLine(
"
Draw a
"
+
_name.ToString());
10
}
11
public
override
void
Add(Graphics g)
12
{
13
//
拋出一個我們自定義的異常
14
}
15
public
override
void
Remove(Graphics g)
16
{
17
//
拋出一個我們自定義的異常
18
}
19
}
這樣改進以后,我們可以捕獲可能出現的錯誤,做進一步的處理。上面的這種實現方法屬於透明式的
Composite
模式,如果我們想要更安全的一種做法,就需要把管理子對象的方法聲明在樹枝構件
Picture
類里面,這樣如果葉子節點
Line
,
Rectangle
,
Circle
使用這些方法時,在編譯期就會出錯,看一下類結構圖:

示意代碼:
1
public
abstract
class
Graphics
2
{
3
protected
string
_name;
4
5
public
Graphics(
string
name)
6
{
7
this
._name
=
name;
8
}
9
public
abstract
void
Draw();
10
}
11
12
public
class
Picture : Graphics
13
{
14
protected
ArrayList picList
=
new
ArrayList();
15
16
public
Picture(
string
name)
17
:
base
(name)
18
{ }
19
public
override
void
Draw()
20
{
21
Console.WriteLine(
"
Draw a
"
+
_name.ToString());
22
23
foreach
(Graphics g
in
picList)
24
{
25
g.Draw();
26
}
27
}
28
29
public
void
Add(Graphics g)
30
{
31
picList.Add(g);
32
}
33
public
void
Remove(Graphics g)
34
{
35
picList.Remove(g);
36
}
37
}
38
39
public
class
Line : Graphics
40
{
41
public
Line(
string
name)
42
:
base
(name)
43
{ }
44
45
public
override
void
Draw()
46
{
47
Console.WriteLine(
"
Draw a
"
+
_name.ToString());
48
}
49
}
50
51
public
class
Circle : Graphics
52
{
53
public
Circle(
string
name)
54
:
base
(name)
55
{ }
56
57
public
override
void
Draw()
58
{
59
Console.WriteLine(
"
Draw a
"
+
_name.ToString());
60
}
61
}
62
63
public
class
Rectangle : Graphics
64
{
65
public
Rectangle(
string
name)
66
:
base
(name)
67
{ }
68
69
public
override
void
Draw()
70
{
71
Console.WriteLine(
"
Draw a
"
+
_name.ToString());
72
}
73
}
這種方式屬於安全式的
Composite
模式,在這種方式下,雖然避免了前面所討論的錯誤,但是它也使得葉子節點和樹枝構件具有不一樣的接口。這種方式和透明式的
Composite
各有優劣,具體使用哪一個,需要根據問題的實際情況而定。通過
Composite
模式,客戶程序在調用
Draw()
的時候不用再去判斷復雜圖像元素中的子對象到底是基本圖像元素,還是復雜圖像元素,看一下簡單的客戶端調用:
1
public
class
App
2
{
3
public
static
void
Main()
4
{
5
Picture root
=
new
Picture(
"
Root
"
);
6
7
root.Add(
new
Line(
"
Line
"
));
8
root.Add(
new
Circle(
"
Circle
"
));
9
10
Rectangle r
=
new
Rectangle(
"
Rectangle
"
);
11
root.Add(r);
12
13
root.Draw();
Composite模式實現要點:
1.Composite模式采用樹形結構來實現普遍存在的對象容器,從而將“一對多”的關系轉化“一對一”的關系,使得客戶代碼可以一致地處理對象和對象容器,無需關心處理的是單個的對象,還是組合的對象容器。
2.將“客戶代碼與復雜的對象容器結構”解耦是Composite模式的核心思想,解耦之后,客戶代碼將與純粹的抽象接口——而非對象容器的復內部實現結構——發生依賴關系,從而更能“應對變化”。
3.Composite模式中,是將“Add和Remove等和對象容器相關的方法”定義在“表示抽象對象的Component類”中,還是將其定義在“表示對象容器的Composite類”中,是一個關乎“透明性”和“安全性”的兩難問題,需要仔細權衡。這里有可能違背面向對象的“單一職責原則”,但是對於這種特殊結構,這又是必須付出的代價。ASP.NET控件的實現在這方面為我們提供了一個很好的示范。
4
.
Composite
模式在具體實現中,可以讓父對象中的子對象反向追溯;如果父對象有頻繁的遍歷需求,可使用緩存技巧來改善效率。