目录
组合模式
引言
在面向对象系统中,我们常常会遇到一类具有“容器”特征的对象,它们在充当普通对象的同时,又可以作为其他对象的容器,这些对象称为容器对象,而那些只能充当普通对象的对象则称为叶子对象。如Windows操作系统中,文件包括不同类型的具体文件和文件夹俩类对象,其中文件夹中可以包含子文件夹,也可以包含文件。文件夹是容器类,而不同类型的的各种文件是叶子类。组合模式将容器对象和叶子对象进行递归组合,用户在使用时无需对他们进行区分,可以一致的对待容器对象和叶子对象。
组合模式重要等级★★★★☆ 组合模式难度等级★★★☆☆
定义
英文定义:"Compose object into tree structure to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.".
组合多个对象形成树形结构以表示“整体-部分”的结构层次。客户端对单个对象(叶子对象)和组合对象(容器对象)的使用具有一致性。
组合模式结构图
组合模式主要包含以下角色:
(1)Conponent(抽象构件):可以是接口或者抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。
(2)Leaf(叶子构件):叶子构件在组合模式中标识叶子节点对象,没有子节点。
(3)Composite(容器构件):容器构件包含子节点,子节点既可以是叶子构件,也可以是容器构件。
组合模式实例-产品生产准备
实例描述
生产某种产品(Product)之前,需要将指定的物料和资源(Resource)准备好,其中物料也可能是某种产品。用组合模式设计该系统。
实例类图
通过分析,该实例类图如下图所示。
代码实现
抽象构件类
abstract class AbstractComponent
{
private string name;
protected List<AbstractComponent> childs = new List<AbstractComponent>();
public string Name { get => name; set => name = value; }
protected AbstractComponent(string name)
{
this.Name = name;
}
public void AddChild(AbstractComponent child)
{
childs.Add(child);
}
public void RemoveChild(AbstractComponent child)
{
childs.Remove(child);
}
public abstract void Produce();
}
产品类Product
class Product : AbstractComponent
{
public Product(string name) : base(name) { }
public override void Produce()
{
Console.WriteLine("[{0}]开始生产", this.Name);
foreach (AbstractComponent c in childs)
{
c.Produce();
}
Console.WriteLine("[{0}]生产完成",this.Name);
}
}
设备资源类Resource
class Resource : AbstractComponent
{
public Resource(string name) : base(name) { }
public override void Produce()
{
Console.WriteLine("生产过程中使用设备:[{0}]",this.Name);
}
}
测试代码
class Program
{
static void Main(string[] args)
{
AbstractComponent p1 = new Product("自行车");
AbstractComponent p2 = new Product("车轮");
AbstractComponent p3 = new Product("车架");
AbstractComponent r1 = new Resource("扳手");
AbstractComponent r2 = new Resource("螺丝刀");
AbstractComponent r3 = new Resource("抛光枪");
p1.AddChild(p2);
p1.AddChild(p3);
p1.AddChild(r3);
p2.AddChild(r2);
p2.AddChild(r1);
p1.Produce();
Console.WriteLine("--------------------------");
p2.Produce();
Console.ReadKey();
}
}
运行结果
组合模式的扩展
透明组合模式
叶子对象和容器对象提供的方法是完全一致的。客户端在使用的时候无需区分构件的类型。客户端在使用的时候可以完全面向抽象编程(即透明)。透明组合模式如下图所示:
安全组合模式
叶子对象和容器对象在本质上是不同的,对于叶子对象,客户端不应该调用任何用于管理成员对象的方法(如AddChild(),RemoveChild()等)。这些方法在运行阶段如果调用,是无意义的,可能会出错。因此,安全组合模式在抽象构件中没有声明任何管理成员对象的方法,而是在容器类中声明,这种做法是安全的,缺点就是不够透明,客户端无法完全面向抽象层编程。安全组合模式如下图所示:
抽象叶子类和抽象容器类
如下图所示,我们对叶子节点和容器节点进行抽象,得到抽象叶子节点和抽象容器节点构件。在这样的结构下,因为我们对节点进行更具体的分类,所以可以将透明组合模式和安全组合模式混合使用,构建出更合理的层次结构。在JAVA AWT/Swing中存在类似结构,很多叶子构件和容器构件都拥有子类,如叶子构件TextComponent又有子类TextField、TextArea等,而容器构件Container又有Panel、Windows等子类;
总结
模式优点
(1)组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易,因为它让客户忽略了层次的差异,而它的结构又是动态的,提供了对象管理的灵活接口,因此组合模式可以方便地对层次结构进行控制。
(2)客户端调用简单,因为组合模式使客户端对单个对象和组合对象的使用具有一致性。
(3)可以形成复杂的树形结构
(4)更容易在组合体中加入对象构件,客户端不必因为加入了新对象构件而更改原有代码。
模式缺点
(1)设计较抽象,如果对象的业务规则很复杂,则实现组合模式具有很大的挑战性,而且不是所有的方法都与叶子对象子类有关联。
(2)很难对容器中的构件进行限制,因为他们来自相同的抽象层,在这种情况下,必须通过在运行时进行类型检查来实现,这个实现过程较为复杂。