组合模式(Composite Pattern)【使用频率:★★★★☆】
1. 概述
组合多个对象形成树形结构,以表示具有“整体-部分”关系的层次结构。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性。
组合模式的关键是定义了一个抽象组件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象组件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。同时容器对象与抽象组件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构。
2. 模式中的角色
2.1 抽象组件类(Component):它可以是接口或抽象类,为叶子组件和容器组件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象组件类中,定义了访问及管理它的子组件的方法;
2.2 叶子组件类(Leaf):在组合模式中,表示叶子节点对象,叶子节点没有子节点,实现了在抽象组件类中定义的行为。
2.3 容器组件类(Composite):在组合模式中,表示容器节点对象,容器节点包含子节点,其子节点可以叶子节点,也可以是容器节点,提供了一个集合用于存储子节点,实现了在抽象组件类中定义的行为,包括访问及管理子组件的方法。
3. 模式解读
3.1 模式的类图
3.2 代码实现
现在以一个实际的例子用C#代码来实现,下面是Unity中的一个Hierarchy层级图,根节点是Root,下面有GameObject,GameObject (1), GameObject (2)三个节点,其中GameObject (1)下又有GameObject_A和GameObject_B两个节点:
using System;
using System.Collections.Generic;
namespace ConsoleApp2
{
class Class14
{
public static void Main(string[] args)
{
DMComposite root = new DMComposite("Root");
// 添加Root下的三个节点
DMLeaf leaf1 = new DMLeaf("GameObject");
DMLeaf leaf2 = new DMLeaf("GameObject (2)");
DMComposite gameObject1 = new DMComposite("GameObject (1)");
root.AddChild(leaf1);
root.AddChild(gameObject1);
root.AddChild(leaf2);
// 添加GameObject (1)下面的两个节点
DMLeaf child1 = new DMLeaf("GameObject_A");
DMLeaf child2 = new DMLeaf("GameObject_B");
gameObject1.AddChild(child1);
gameObject1.AddChild(child2);
// 按照广度优先或深度优先输出节点顺序
//DepthFirstSearch(root);
BreadthFirstSearch(root);
Console.ReadLine();
}
// 广度优先检索
private static void BreadthFirstSearch(DMComponent component)
{
Queue<DMComponent> q = new Queue<DMComponent>();
q.Enqueue(component);
Console.WriteLine(component.Name);
while (q.Count > 0)
{
DMComponent temp = q.Dequeue();
List<DMComponent> children = temp.Children;
foreach (DMComponent child in children)
{
Console.WriteLine(child.Name);
q.Enqueue(child);
}
}
}
// 深度优先检索
private static void DepthFirstSearch(DMComponent component)
{
Console.WriteLine(component.Name);
List<DMComponent> children = component.Children;
if (children == null || children.Count == 0) return;
foreach (DMComponent child in children)
{
DepthFirstSearch(child);
}
}
}
// 抽象组件类
public abstract class DMComponent
{
protected string mName;
public string Name { get { return mName; } }
public DMComponent(string name)
{
mName = name;
mChildren = new List<DMComponent>();
}
protected List<DMComponent> mChildren;
public List<DMComponent> Children { get { return mChildren; } }
public abstract void AddChild(DMComponent c);
public abstract void RemoveChild(DMComponent c);
public abstract DMComponent GetChild(int index);
}
// 叶子组件类
public class DMLeaf : DMComponent
{
public DMLeaf(string name) : base(name) { }
public override void AddChild(DMComponent c)
{
return;
}
public override void RemoveChild(DMComponent c)
{
return;
}
public override DMComponent GetChild(int index)
{
return null;
}
}
// 容器组件类
public class DMComposite : DMComponent
{
public DMComposite(string name) : base(name) { }
public override void AddChild(DMComponent c)
{
mChildren.Add(c);
}
public override void RemoveChild(DMComponent c)
{
mChildren.Remove(c);
}
public override DMComponent GetChild(int index)
{
return mChildren[index];
}
}
}
广度优先搜索:
深度优先搜索:
4、 模式优缺点
4.1 优点
- 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易。
- 客户端调用简单,客户端可以一致的使用组合结构或其中单个对象。
- 定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构。
- 更容易在组合体内加入对象构件,客户端不必因为加入了新的对象构件而更改原有代码。
4.2 缺点
使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联
5、 模式适用场景
- 需要表示一个对象整体或部分层次,在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,可以一致地对待它们。
- 让客户能够忽略不同对象层次的变化,客户端可以针对抽象构件编程,无须关心对象层次结构的细节。
- 用组合模式实现Unity中游戏物体父子关系的管理。