设计模式---组合模式
1. 定义
大名鼎鼎的套娃模式
组合模式有时又称部分-整体模式(Part-Whole)
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
——《设计模式》GoF
- 用来描述整体与部分的关系
- 将对象组织到树结构中
- 可以使客户端将单个对象与组合对象统一处理。
- 对象的树结构
一个树结构由两种节点组成:树枝节点和树叶节点。树枝节点可以有子节点,而一个树叶节点不可以有子节点。除了根节点外,其它节点有且只有一个父节点。
2. 为什么要用组合模式
- 对象容器的问题(对象树中的分支节点)
在面向对象系统中,我们常会遇到一类具有“容器”特征的对象——即它们在充当对象的同时,又是其他对象的容器.。
- 在客户端往往需要对容器对象进行复杂的处理:
- 前面的客户代码里面必须要知道组合对象的结构,有可能还要使用递归的方法来处理这个对象,这样写耦合性就比较高。客户代码如果能只和IBox发生依赖就会好多了,但是现在它还和ContainerBox和SingleBox发生了依赖,这样2个具体的Box类内部实现的细节就暴露给了外界,并且和外界产生了依赖关系。
- 上述问题根源在于:客户代码过多地依赖于具体对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性、扩展性等弊端。如何将“客户端代码与复杂的对象容器结构”解耦?让对象容器自己来实现自身的复杂结构(SRP),从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?
3. 两种解决方案
3.1 整体架构
- 抽象构件(Component)角色:这是一个抽象角色,它给参与组合的对象规定一个接口。这个角色给出共有接口及其默认行为。
- 树叶构件(Leaf)角色:代表参加组合的树叶对象。一个树叶对象没有下级子对象。
- 树枝构件(Composite)角色:代表参加组合的有子对象的对象,给出树枝构件对象的行为。Composite类型的对象可以包含其它Component类型的对象。换而言之,Composite类型对象可以含有其它的树枝(Composite)类型或树叶(Leaf)类型的对象。
组合模式的实现根据所实现接口的区别分为两种形式,分别称为安全模式和透明模式。
3.2 安全模式
安全式的组合模式要求管理组合对象的方法只出现在树枝构件类中,而不出现在树叶构件中。
组合模式可以不提供父对象的管理方法,但组合模式必须在合适的地方提供子对象的管理方法(诸如:add、remove、getChild等)
在Composite类里面声明所有的用来管理子类对象的方法。这样的做法是安全的做法,因为树叶类型的对象根本就没有管理子类对象的方法,因此,如果客户端对树叶类对象使用这些方法时,程序会在编译时期出错。
这个选择的缺点是不够透明,因为树叶类和组合类将具有不同的接口。
- 抽象构件(Component)角色:这是一个抽象角色,它给参加组合的对象定义出公共的接口及其默认行为,可以用来管理所有的子对象。
- 树叶构件(Leaf)角色:树叶对象是没有下级子对象的对象,定义出参加组合的原始对象的行为。
- 树枝构件(Composite)角色:代表参加组合的有下级子对象的对象。树枝对象给出所有的管理子对象的方法,如add()、remove()、getChild()等。
namespace SafeComposite
{
public abstract class Component
{
// Fields
protected string name;
// Constructors
public Component(string name)
{
this.name = name;
}
// Operation
public abstract void Display(int depth);
}
}
namespace SafeComposite
{
class Composite : Component
{
// Fields
private ArrayList children = new ArrayList();
// Constructors
public Composite(string name) : base(name) { }
// Methods for managing--分支节点专有的方法
public void Add(Component component)
{
children.Add(component);
}
public void Remove(Component component)
{
children.Remove(component);
}
public override void Display(int depth)
{
Console.WriteLine(new String('-', depth) + name);
// Display each of the node's children
foreach (Component component in children)
component.Display(depth + 2);
}
}
}
namespace SafeComposite
{
class Leaf : Component
{
// Constructors
public Leaf(string name) : base(name) { }
// Methods
public override void Display(int depth)
{
Console.WriteLine(new String('-', depth) + name);
}
}
}
namespace SafeComposite
{
class Program
{
static void Main(string[] args)
{
// 注意此处不能使用Component,因为它不能对子节点进行管理操作
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);
root.Add(new Leaf("Leaf C"));
// Add and remove a leaf
Leaf l = new Leaf("Leaf D");
//l.Add(new Leaf("xxxxxx")); //Compiling Error!!!
root.Add(l);
root.Remove(l);
// Recursively display nodes
root.Display(0);
Console.ReadLine();
}
}
}
3.3 透明模式
透明式的组合模式要求所有的具体构件类,不论树枝构件还是树叶构件,均符合一个固定的接口。
在Component里面声明所有的用来管理子类对象的方法,包括add()、remove(),以及getChild()方法。这样做的好处是所有的构件类都有相同的接口。在客户端看来,树叶类对象与组合类对象的区别起码在接口层次上消失了,客户端可以同等的对待所有的对象。
这种方式的缺点是不够安全,因为树叶类对象和组合类对象在本质上是有区别的。树叶类对象不可能有下一个层次的对象,因此add()、remove()以及getChild()方法没有意义,这样做即便在编译时期不会出错,但是在运行时期会出错。
- 抽象构件(Component)角色:这是一个抽象角色,它给参加组合的对象规定一个接口,规范共有的接口及默认行为。
- 树叶构件(Leaf)角色:代表参加组合的树叶对象,定义出参加组合的原始对象的行为。树叶类会给出add()、remove()以及getChild()之类的用来管理子类对对象的方法的平庸实现。
- 树枝构件(Composite)角色:代表参加组合的有子对象的对象,定义出这样的对象的行为。
namespace TransparentComposite
{
public abstract class Component
{
// Fields
protected string name;
// Constructors
public Component(string name)
{ this.name = name; }
// 子类对象的管理方法
abstract public void Add(Component c);
abstract public void Remove(Component c);
//与业务相关的操作
abstract public void Display(int depth);
}
}
namespace TransparentComposite
{
class Composite : Component
{
// Fields
private ArrayList children = new ArrayList();
// Constructors
public Composite(string name) : base(name) { }
// Methods
public override void Add(Component component)
{ children.Add(component); }
public override void Remove(Component component)
{ children.Remove(component); }
public override void Display(int depth)
{
Console.WriteLine(new String('-', depth) + name);
// Display each of the node's children
foreach (Component component in children)
component.Display(depth + 2);
}
}
}
namespace TransparentComposite
{
class Leaf : Component
{
// Constructors
public Leaf(string name) : base(name) { }
// Methods
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); }
}
}
namespace TransparentComposite
{
class Program
{
static void Main(string[] args)
{
// Create a tree structure
Component root = new Composite("root");
root.Add(new Leaf("Leaf A"));
root.Add(new Leaf("Leaf B"));
Component comp = new Composite("Composite X");
comp.Add(new Leaf("Leaf XA"));
comp.Add(new Leaf("Leaf XB"));
root.Add(comp);
root.Add(new Leaf("Leaf C"));
// Add and remove a leaf
Component l = new Leaf("Leaf D");
l.Add(new Leaf("xxxx")); //Executing Error!!!
root.Add(l);
root.Remove(l);
// Recursively display nodes
root.Display(0);
Console.ReadLine();
}
}
}
4. 总结
-
Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致地处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。
-
将“客户端代码与复杂的对象容器结构”解耦是Composite模式的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的复杂内部实现结构——发生依赖关系,从而更能“应对变化”。
-
Composite模式中,是将“Add和Remove等和对象容器相关的方法”定义在“表示抽象对象的Component类”中,还是将其定义在“表示对象容器的Composite类”中,是一个关乎“透明性”和“安全性”的两难问题,需要仔细权衡。透明方式违背面向对象的“单一职责原则”,但是对于这种特殊结构,这又是必须付出的代价。
-
微软在.Net中采用了另外的方式来处理这个两难问题: Controls
示例: DotNetComposite
namespace DotNetComposite
{
/// <summary>
/// 抽象节点
/// </summary>
abstract class Component
{
/// <summary>
/// 子节点列表
/// </summary>
public List<Component> Children;//利用此列表来屏蔽安全性和透明性问题--客户端程序直接对此表进行添加和删除
// Fields
protected string name;
// Constructors
public Component(string name)
{
this.name = name;
Children = new List<Component>();//创建子节点列表--叶子和分支统一对待--区别是叶子节点的列表肯定为空
}
// Operation
public abstract void Display(int depth);
}
}
namespace DotNetComposite
{
/// <summary>
/// 分支节点
/// </summary>
class Composite:Component
{
// Constructors
public Composite(string name) : base(name) { }
public override void Display(int depth)
{
Console.WriteLine(new String('-', depth) + name);
//此处需要判断是否含有字节点--有的话,需要依次处理
if(Children.Count>0)
foreach (Component component in Children)
component.Display(depth + 2);
}
}
}
namespace DotNetComposite
{
/// <summary>
/// 叶子节点
/// </summary>
class Leaf:Component
{
// Constructors
public Leaf(string name) : base(name) { }
//此处只处理叶子节点的数据,不会理会是否含有子节点(应该没有子节点)
public override void Display(int depth)
{
Console.WriteLine(new String('-', depth) + name);
}
}
}
namespace DotNetComposite
{
class Program
{
static void Main(string[] args)
{
Component root = new Composite("root");
root.Children.Add(new Leaf("Leaf A"));
root.Children.Add(new Leaf("Leaf B"));
Component comp = new Composite("Composite X");
comp.Children.Add(new Leaf("Leaf XA"));
comp.Children.Add(new Leaf("Leaf XB"));
root.Children.Add(comp);
root.Children.Add(new Leaf("Leaf C"));
// Add and remove a leaf
Leaf l = new Leaf("Leaf D");
l.Children.Add(new Leaf("xxxxxx")); //此处添加子节点无意义--添加不会出任何问题,但是无用--处理方法中不含有处理子节点的代码
root.Children.Add(l);
root.Children.Remove(l);
// Recursively display nodes
root.Display(0);
Console.ReadLine();
}
}
}
可以看出,将Composite类中的集合类型的数据结构放在抽象类中,使叶子类也具有这个集合,但是增删子节点对叶子类不作任何操作,可以做到在客户端透明操作,又不会报错。
本文地址:https://blog.csdn.net/qq_41342326/article/details/107481091