问题:
在某些情况下,
一类具有“容器特征”的对象在充当对象的同时,又是其他对象的容器的情况,比如树状结构的对象,如果客户过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构的变化将引起客户代码的频繁变化。如何将“客户代码与复杂的对象容器结构”解耦?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?
定义:
组合(Composite )模式是构造型的设计模式之一,是指将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得客户对单个对象和组合对象的使用具有一致性。
意图:
提供一个Component角色,它定义了访问与管理部件(部分-整体)的通用接口一般包含Add(),Remove()等方法。定义一个用户存储子部件的Composite角色,他实现了Component角色提供访问与管理部件的所有方法。并维护了一个Component角色的引用列表,用于管理维护所有的子部件。这样使得基础对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断地递归下去就形成了一个树形结构的复杂对象。组合模式就是采用树形结构来实现普遍存在的对象容器,从而将一对多的关系转化一对一的关系,使得客户代码可以一致地处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。将客户程序与对象内部实现解耦,让对象容器自己来实现自身的复杂结构,解耦后,客户程序只能对象上层的接口发生关系。
参与者:
•抽象组件角色(Component):
是组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。
•叶组件角色(Leaf):
在组合中表示叶节点对象没有子节点,实现抽象组件角色声明的接口。
•子组件角色(Composite):
在组合中表示分支节点对象,用来存储子部件,有子节点,实现抽象组件角色声明的接口如增加(add)和删除(remove)等。
UML:
代码说明:
/// 抽象组件角色(Component)
/// </summary>
public abstract class Component
{
public string Name { get; set; }
public abstract void Add(Component component);
public abstract void Remove(Component component);
public abstract void Show();
}
/// <summary>
/// 叶组件角色(Leaf)
/// </summary>
public class Leaf : Component
{
public override void Add(Component component)
{
System.Console.WriteLine("我是一个叶子节点,不能添加子对象");
}
public override void Remove(Component component)
{
System.Console.WriteLine("我是一个叶子节点,没有子对象");
}
public override void Show()
{
System.Console.WriteLine("我是一个叶子节点。");
}
}
/// <summary>
/// 子组件角色(Composite)
/// </summary>
public class CompositeClass : Component
{
List<Component> list = new List<Component>();
public override void Add(Component component)
{
list.Add(component);
}
public override void Remove(Component component)
{
list.Remove(component);
}
public override void Show()
{
foreach (var s in list)
{
System.Console.WriteLine(String.Format("我是{0}节点。",s.Name));
}
}
}
/// <summary>
/// 客户端测试
/// </summary>
public void CompositeTest()
{
Component obj = new CompositeClass();
obj.Name = "我是个节点";
Component obj1 = new CompositeClass() { Name = "我还是一个节点" };
Component obj2 = new CompositeClass() { Name = "我还是一个节点" };
obj2.Add(new Leaf() { Name="我是一个叶子节点" });
obj.Add(obj1);
obj.Add(obj2);
}
优点:
•客户代码与复杂的对象容器结构解耦,让对象容器自己来实现自身的复杂结构。添加或移除对象不必修改客户端代码。
•单个对象和组合对象具有一致性接口,所以用户不必关系自己处理的是单个对象还是整个组合结构,这就简化了客户端代码。
适用场合:
•表示对象的部分-整体层次结构(树形结构)。
•希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
透明方式与安全方式:
透明方式(客户端不关系,不知道使用的是Composite还是Leaf):
在Component中声明所有用来来管理子对象的方法,包含Add(),Remove()等,这样喜欢Component接口所有的子对象都具备Add(),Remove()等,这样能达到GoF所说的“单个对象和组合对象的使用具有一致性”可是对于Leaf节点不具备Add(),Remove()等操作,这个实现是没有意义的。
安全方式:
在Composite中声明所有用来来管理子对象的方法,不在Leaf中定义和实现不需要的操作,这样会使Composite与Leaf不具有相同的接口,客户端调用需要做相应的判断。
GoF比较倾向于:以不遵守单一责任原则换取透明性,让Client将组合和叶节点具有一致性。
在实现组合模式时,有很多设计上的折衷。要根据需求平衡透明性和安全性。