组合模式(Composite)

First Post:

Last Update:

组合模式(Composite)

如果将总公司当作一颗大树的根部的话,那么对于它的下属分公司其实是这棵树的分支,至于其他更小的职能部门,则是更小处的分支以至树叶。

组合模式(Composite),将对象组合成树形结构以表示“部分-整体”的结构层次。组合模式使得用户对单个对象和组合对象的使用具有一致性。

组合模式结构图

组合模式结构图

组合模式成员分析

Component——组合中的对象声明接口,在适当情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component的子部件。

Composite——定义有枝节点行为,用来存储子部件,在Component接口中实现与子部件有关的操作,例如增加Add和删除Remove。

Leaf——定义叶子节点行为,在组合中表示叶节点对象,叶节点没有子节点。叶子节点实现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
85
86
87
88
89
90
91
92
组合模式代码结构:
/// <summary>
/// Component为组合中的对象声明接口,在适当情况下,
/// 实现所有类共有接口的默认行为。
/// 声明一个接口用于访问和管理Component的子部件
/// </summary>
abstract class Component
{
protected string name;
public Component(string name)
{ this.name = name; }
public abstract void Add(Component c);
public abstract void Remove(Component c);
//通常用Add和Remove两方法来提供增加或移除树叶的办法
public abstract void Display(int depth);
}


/// <summary>
/// Leaf表示叶节点对象,叶节点没有子节点
/// </summary>
class Leaf : Component
{
public Leaf(string name) : base(name) { }

public override void Add(Component c)
{
//叶子节点实现Add和Remove接口没有任何意义,因为它们没法再添加分支和树叶。
//实现它们可以消除叶节点和枝节点对象在抽象层次的区别,使它们具备一样的接口。
throw new ArgumentException("叶节点无法增加分支");
}
public override void Remove(Component c)
{
throw new ArgumentException("叶节点没有子节点,不能移除其子节点");
}
/// <summary>
/// 叶子的具体方法,此次提供一个展示深度和名字的方法。
/// </summary>
/// <param name="depth"></param>
public override void Display(int depth)
{
Console.WriteLine(new string('-',depth) + name);
}
}


/// <summary>
/// Composite对象定义枝节点行为,用来存储子部件,
/// 在Component接口中实现有关与子部件的操作,
/// 比如增加Add和删除Remove
/// </summary>
class Composite : Component
{
//声明一个子节点集合用来存储其下属的枝节点和叶节点。
private List<Component>children = new List<Component>();

public Composite(string name) : base(name) { }

public override void Add(Component c)
{
children.Add(c);
}

public override void Display(int depth)
{
Console.WriteLine(new string('-', depth) + name);
foreach (Component component in children)
{
component.Display(depth + 2);
}
}

public override void Remove(Component c)
{
children.Remove(c);
}
}

客户端代码
//生成树根root
Component root = new Composite("root");
//为树根添加一个树叶
root.Add(new Leaf("Leaf A"));
//为树根添加一个节点
Composite composite1 = new Composite("Composite X");
root.Add(composite1);
//为分支节点添加叶节点和分支节点
composite1.Add(new Leaf("Leaf B"));
Composite composite2 = new Composite("Composite Y");
composite1.Add(composite2);
root.Display(1);
Console.Read();

运行结果

组合模式总结

为了使整体和部分能够被一致对待,可以采用组合模式

有一个问题——为什么树叶Leaf中也有Add和Remove呢?

透明方式,也就是说在Component中声明所有用来管理子对象的方法,其中包括Add和Remove等。这样实现Component接口的所有子对象都具备了Add和Remove等。这样使得叶节点和根节点对于外界没有区别,它们具备一致的行为接口。问题是:实现叶节点的Add和Remove是没有意义的。
安全方式,也就是在Component不提供Add和Remove接口方法,那么子类的Leaf也就不需要实现它,而是在Composite声明所有用来管理子类的方法。这样就导致了不够透明,所有树叶类和树枝类将不具备相同的接口,客户端的调用需要做相应的判断,带来了不便。

何时使用组合模式?

当发现需求中是体现部分与整体层次的结构时,你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑使用组合模式。

使用组合模式有何好处?

  • 组合模式定义包含了基本对象和组合对象的类层次结构。
  • 基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断地递归下去,客户代码中,任何使用到基本对象的地方都可以使用组合对象了。
  • 用户不用关心到底是处理一个叶节点还是处理一个组合部件,也就用不着定义组合而写一些选择判断语句了。
  • 组合模式使得用户可以一致地使用组合结构和单个对象。

用到游戏开发中就是组件模式

组件模式是将独立的功能点做一个封装,便于增删和管理。

组件模式一般有两种,一种就是组合(就是经常拿来跟继承对比),另一种是属性扩展,就是将组件中的所有方法和属性,扩展到载体中。

例如,寻路组件,刚体组件等。

Unity中各种的Component就是组件,Unity就是组件的集合。