19. 组合模式
约 1389 字大约 5 分钟
2026-03-18
定义
组合模式(Composite),将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
人话:组合模式就像一个公司的组织结构。比如一个公司有一个总部,总部下面有财务部、人力资源部,还可能有多个分公司。而每个分公司下面,又可以继续有自己的财务部、人力资源部,甚至还有自己的子分公司。
如果不用组合模式来设计,程序里可能就要写很多判断,比如“这是总部还是部门?”、“这个节点下面还有没有子节点?”、“是不是分公司?”等等。随着层级越来越复杂,这些 if-else 判断会越来越多,代码也会变得非常混乱,这在软件中就属于结构复杂、难以扩展。
而使用组合模式之后,我们会把“总部”“分公司”和“部门(财务、人力)”都当作一种统一的对象来看待。总部和分公司属于“组合节点”(可以包含下级),而财务部、人力资源部属于“叶节点”(没有下级)。它们都实现同一套接口,比如“添加子节点”“移除子节点”“展示结构”等。
这样一来,总部可以添加分公司和部门,分公司也可以继续添加自己的部门或子公司,而部门虽然不能再添加下级,但在使用时和其他节点是统一的。客户端只需要面向统一接口操作,就可以构建出一棵完整的公司组织结构树。
在软件中也是一样:组合模式让我们可以用一致的方式处理“整体”和“部分”,不用关心当前操作的是总部、分公司还是具体部门,从而让层级结构清晰、扩展方便。
组合模式(Composite)结构图
Component类——抽象组件:
// Component(抽象组件)
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);
// 显示结构
public abstract void Display(int depth);
}Leaf类——叶节点:
// Leaf(叶节点)
class Leaf : Component
{
public Leaf(string name) : base(name)
{
}
public override void Add(Component c)
{
Console.WriteLine("Cannot add to a leaf");
}
public override void Remove(Component c)
{
Console.WriteLine("Cannot remove from a leaf");
}
// 显示叶节点
public override void Display(int depth)
{
Console.WriteLine(new string('-', depth) + name);
}
}Composite类——组合节点(树枝):
// Composite(组合节点)
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 Remove(Component c)
{
children.Remove(c);
}
public override void Display(int depth)
{
Console.WriteLine(new string('-', depth) + name);
foreach (Component component in children)
{
component.Display(depth + 2);
}
}
}客户端代码:
static void Main(string[] args)
{
// 生成树根
Composite root = new Composite("root");
// 根上长出两叶
root.Add(new Leaf("Leaf A"));
root.Add(new Leaf("Leaf B"));
// 根上长出一个分支
Composite comp = new Composite("Composite X");
comp.Add(new Leaf("Leaf XA"));
comp.Add(new Leaf("Leaf XB"));
root.Add(comp);
// 分支上再长出子分支
Composite comp2 = new Composite("Composite XY");
comp2.Add(new Leaf("Leaf XYA"));
comp2.Add(new Leaf("Leaf XYB"));
comp.Add(comp2);
// 根部再长叶
root.Add(new Leaf("Leaf C"));
Leaf leaf = new Leaf("Leaf D");
root.Add(leaf);
// Leaf D 被移除
root.Remove(leaf);
// 显示结构
root.Display(1);
Console.Read();
}运行结果:
-root
--Leaf A
--Leaf B
--Composite X
----Leaf XA
----Leaf XB
----Composite XY
------Leaf XYA
------Leaf XYB
--Leaf C为什么Leaf类当中也有Add和Remove,树叶不是不可以再长分枝吗?这种方式叫做透明方式,也就是说在Component中声明所有用来管理子对象的方法,其中包括Add、Remove等。这样实现Component接口的所有子类都具备了Add和 Remove。这样做的好处就是叶节点和枝节点对于外界没有区别,它们具备完全一致的行为接口。但问题也很明显,因为Leaf类本身不具备Add0、Removeo方法的功能,所以实现它是没有意义的。
Leaf类当中不用Add和Remove方法,可以吗?可以,那么就需要安全方式,也就是在Component接口中不去声明Add和Remove方法,那么子类的Leaf也就不需要去实现它,而是在Composite声明所有用来管理子类对象的方法,这样做就不会出现刚才提到的问题,不过由于不够透明,所以树叶和树枝类将不具有相同的接口,客户端的调用需要做相应的判断,带来了不便。
什么地方用组合模式比较好呢?当你发现需求中是体现部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑用组合模式了。
