组合模式介绍
组合模式是把相似对象或方法组合成一组可被调用的结构树对象的设计思路。
组合模式不只是可以运用于规则决策树,还可以做服务包装将不同的接口进行组合配置,对外提供服务能力,减少开发成本。
组合模式的主要解决的是一系列简单逻辑节点或者扩展的复杂逻辑节点在不同结构的组织下,对于外部的调用是仍然可以非常简单的。
组合模式的结构
- 组件 接口描述了树中简单项目和复杂项目所共有的操作。
- 叶节点 是树的基本结构,它不包含子项目。 一般情况下,叶节点最终会完成大部分的实际工作,因为它们无法将工作指派给其他部分。
- 容器 又名组合,是包含叶节点或其他容器等子项目的单位。容器不知道其子项目所属的具体类,它只通过通用的接口与其子项目交互。容器接收到请求后会将工作分配给自己的子项目,处理中间结果,然后将最终结果返回客户端。
- 客户端 通过组件接口与所有项目交互。因此,客户端能以相同方式与树状结构中的简单或复杂项目交互。
组合模式的应用场景
- 当业务是需要实现树状对象结构,可以使用组合模式。
组合模式提供两种共享公共接口的基本元素类型:简单叶节点和复杂容器。容器中可以包含叶节点和其他容器。这样就可以构建树状嵌套递归对象结构。
- 当希望客户端代码以相同方式处理简单和复杂元素,可以使用组合模式
组合模式中定义的所有元素共用同一个接口。正式由于这接口,客户端不必在意其所使用的对象的具体类。
组合模式的优缺点
优点:
1、可以利用多态和递归机制更方便地使用复杂树结构
2、开闭原则,无需更改现有代码,就可以在应用中添加新元素,使其成为对象树的一部分。
缺点:
1、对于功能差异大的类,提供公共接口或许会有困难。
实现方式
1、确保应用的核心模型能够以树状结构表示,尝试将其分解为简单元素和容器。其中容器必须同时包含简单元素和其他容器。
2、声明组件接口及其一系列方法,这些方法对简单和复杂元素都有意义。
3、创建一个叶节点类表示简单元素,程序中可以有多个不同的叶节点类。
4、创建一个容器类表示复杂元素。在该类中,创建一个数组成员变量来存储对于其子元素的引用。该数组必须能够同时保存叶节点和容器,因此请确保将其声明为组合接口类型。
实现组件接口方法时,记住容器应该将大部分工作交给其子元素来完成。
5、在容器中定义添加和删除子元素的方法。
这些操作可在组件接口中声明。会违背“接口隔离原则”,因为叶节点类中的这些方法为空。但是可以让客户端无差别地访问所有元素,即使是组成树状结构的元素。
Demo
/// <summary> /// 为简单对象和复杂对象声明了通用操作 /// </summary> abstract class Component { public Component() { } /// <summary> /// 操作变量 /// </summary> /// <returns></returns> public abstract string Operation(); public virtual void Add(Component component) { throw new NotImplementedException(); } public virtual void Remove(Component component) { throw new NotImplementedException(); } /// <summary> /// 是否是复合 /// </summary> /// <returns></returns> public virtual bool IsComposite() { return true; } }
/// <summary> /// 叶 /// </summary> class Leaf:Component { public override string Operation() { return "Leaf"; } public override bool IsComposite() { return false; } }
/// <summary> /// 复合对象(里面既包含叶又包含小复合对象) /// </summary> class Composite:Component { protected List<Component> _children = new List<Component>(); public override void Add(Component component) { this._children.Add(component); } public override void Remove(Component component) { this._children.Remove(component); } public override string Operation() { int i = 0; string result = "包含的分支都有那些:"; foreach (var component in _children) { result += component.Operation(); if(i!=_children.Count-1) { result += "+"; } i++; } return result + ")"; } }
/// <summary> /// 客户端 /// </summary> class Client { /// <summary> /// 组件调用接口 叶子调用 /// </summary> /// <param name="leaf"></param> public void ClientCode(Component leaf) { Console.WriteLine("Result:"+leaf.Operation()); } /// <summary> /// 在基组件类中,客户端可以与任何组件,简单和复杂的对象交互,不依赖具体的类 /// </summary> /// <param name="c1"></param> /// <param name="c2"></param> public void ClientCode2(Component c1,Component c2) { if (c1.IsComposite()) { c1.Add(c2); } Console.WriteLine("Result:"+c1.Operation()); } }
class Program { static void Main(string[] args) { Client client = new Client(); Leaf leaf = new Leaf(); Console.WriteLine("调用叶子......"); client.ClientCode(leaf); Console.WriteLine("---------------"); Composite tree = new Composite(); Composite branch1 = new Composite(); branch1.Add(new Leaf()); branch1.Add(new Leaf()); Composite branch2 = new Composite(); branch2.Add(new Leaf()); tree.Add(branch1); tree.Add(branch2); Console.WriteLine("调用复杂对象......"); client.ClientCode(tree); Console.WriteLine("---"); client.ClientCode2(tree,leaf); Console.ReadKey(); } }
从计算结果可以看出,当第一次只调用叶子时,结果就只显示叶子,也就是简单元素,当第二次声明实例化复杂容器(包含叶子和别的容器)时,输出结果也可以把所有声明实例的容器中的所有叶子和子容器都输出显示。
其实对于我们业务来说,需要把握好业务的逻辑看到底需要并适合那种模式,这样才能不为了模式而模式代码。