C# 结构型设计模式----组合模式
1、简介
组合模式允许将对象组合成树形结构以表示“整体/部分”层次结构。使用此模式,客户端可以按相同的方式处理单个对象或者对象集合,而不必关注它们是单个对象还是组合对象。组合对象本身也可以作为容器,包含其他组合对象,形成更复杂的树形结构。
在C#中,组合模式是一种递归嵌套的设计模式,通常需要使用抽象类或接口表示“整体”和“部分”之间的关系,并将部件对象存储在它们的容器中。通过将容器中的部件继续使用相同的方式处理,客户端代码可以逐级访问嵌套对象,而不必知道每个对象的具体类型或是否是叶子节点。
可以简单的把组合模式分为三个部分:
- 抽象组件类(Component):它可以是接口或抽象类(可以理解为树干),为节点组件和容器组件对象声明接口,在该类中包含共有行为的声明。在抽象组件类中,定义了访问及管理它的子组件的方法。
- 节点组件类(Leaf):节点对象为最小组件(可以理解为树叶),并继承自抽象组件类,实现其共有声明和方法。
- 容器组件类(Composite):容器对象可以包含无数节点对象和无数容器组件(可以理解为树枝,可以有无数树叶或者分支),容器对象需要实现管理子对象的方法,如Add、Remove等。
优点:
可以方便地处理树状结构,具有一致性和可维护性。
组合对象可以递归嵌套,允许动态的添加和删除节点和树形结构。
通过共享相同接口或抽象类,客户端代码可以无缝切换一个元素与多个元素之间的关系,从而简化代码逻辑。
允许在叶子和组合对象中分别添加新的行为和操作,而不会影响其它部分的代码。缺点:
可能难以限制容器中的元素类型,会产生一定的安全隐患。
由于递归嵌套,可能对内存和性能有一定的影响。
当组合对象拥有大量子节点时,可能会对代码可读性和理解性造成一定的困难。
2、适用场景
组合模式可以方便地处理层次结构,例如组织机构、文件系统或UI控件。使用该模式,可以将树形数据结构的遍历变得简单且具有一致性,而无论遍历哪个节点,只需按照相同的方式进行。
使用组合模式还可以使代码更加灵活。由于容器和叶子节点可以互换使用,可以轻松地添加新的叶子节点和容器对象,而不会影响其它部分代码的实现。
3、具体实现
组合模式分两种实现方式:透明式和安全式。
所谓透明式就是抽象组件类(Component)定义了节点组件类(Leaf)与 容器组件类(Composite)所包含的全部方法,无论调用容器对象还是叶子对象,接口方法都是一样的,这就是透明,针对客户端代码的透明,但这样不管是节点组件还是容器组件都有可能必须实现一些无用的方法。
安全式则是在抽象组件类(Component)仅定义节点组件类(Leaf)与 容器组件类(Composite)共有的方法,其各自特有的方法仅在其内部自行抽象出来定义。
下面以文件系统为例分别实现两种方式
透明式:
抽象组件类(Component)
/// <summary>
/// 抽象类-声明容器对象(Composite)与叶子对象(Leaf)所有的方法
/// </summary>
public abstract class Component
{
/// <summary>
/// 定义该对象名称
/// </summary>
public string Name { get; set; }
private List<Component> _components;
/// <summary>
/// 用来保存所有子节点对象
/// </summary>
public List<Component> components
{
get { return _components; }
set { _components = value; }
}
public Component(string name)
{
this.Name = name;
components = new List<Component>();
}
/// <summary>
/// 打开方法
/// </summary>
public abstract void Open();
/// <summary>
/// 下一页
/// </summary>
public abstract void Next();
/// <summary>
/// 上一页
/// </summary>
public abstract void Up();
/// <summary>
/// 添加
/// </summary>
public abstract void Add(Component component);
/// <summary>
/// 移除
/// </summary>
public abstract void Remove(Component component);
}
容器组件类(Composite)
/// <summary>
/// 容器对象-层级容积。这里代表文件夹对象
/// </summary>
public class Composite : Component
{
public Composite(string name) : base(name)
{ }
public override void Add(Component component)
{
Console.WriteLine($"{Name}添加了{component.Name}");
components.Add(component);
}
public override void Next()
{
Console.WriteLine("下一节");
}
public override void Open()
{
Console.WriteLine("打开文件夹");
}
public override void Remove(Component component)
{
Console.WriteLine($"{Name}移除了{component.Name}");
components.Remove(component);
}
public override void Up()
{
Console.WriteLine("上一节");
}
}
节点组件类(Leaf)
/// <summary>
/// 叶子对象-最终节点代表各种类型的文件
/// </summary>
public class Leaf : Component
{
public Leaf(string name) : base(name)
{
}
/// <summary>
/// 这些方法就属于多余但必须实现的方法
/// </summary>
/// <param name="component"></param>
public override void Add(Component component)
{
}
public override void Next()
{
}
public override void Open()
{
Console.WriteLine("打开文件");
}
public override void Remove(Component component)
{
}
public override void Up()
{
}
}
客户端调用
private void WTBtn_Click(object sender, EventArgs e)
{
Component composite1 = new Composite("一级文件夹");
Console.WriteLine("创建一个一级文件夹");
Component composite2 = new Composite("二级文件夹");
Console.WriteLine("创建一个二级文件夹");
Component composite3 = new Composite("三级文件夹");
Console.WriteLine("创建一个三级文件夹");
composite2.Add(composite3);//先将三级文件夹装到二级文件夹下
composite1.Add(composite2);//再将二级文件夹装到一级文件夹下
Component leaf1 = new Leaf("一个文档");
Console.WriteLine("创建一个一个文档");
Component leaf2 = new Leaf("一个表格");
Console.WriteLine("创建一个一个表格");
composite2.Add(leaf1);//将文档装到二级文件夹
composite3.Add(leaf2);//将表格装到三级文件夹
Console.WriteLine("-----------输出一级文件夹下内容--------------");
composite1.components.ForEach(c =>
{
Console.WriteLine(c.Name);
});
Console.WriteLine("-----------输出二级文件夹下内容--------------");
composite2.components.ForEach(c => {
Console.WriteLine(c.Name);
});
Console.WriteLine("-----------输出三级文件夹下内容--------------");
composite3.components.ForEach(c => {
Console.WriteLine(c.Name);
});
Console.WriteLine("-----------展示一级文件夹深度检索内容--------------");
DepthFirstSearch(composite1);
Console.WriteLine("-----------展示二级文件夹深度检索内容--------------");
DepthFirstSearch(composite2);
}
/// <summary>
/// 广度优先检索
/// </summary>
/// <param name="component"></param>
private static void BreadthFirstSearch(Component component)
{
Queue<Component> q = new Queue<Component>();
q.Enqueue(component);
Console.WriteLine(component.Name);
while (q.Count > 0)
{
Component temp = q.Dequeue();
List<Component> children = temp.components;
foreach (Component child in children)
{
Console.WriteLine(child.Name);
q.Enqueue(child);
}
}
}
/// <summary>
/// 深度优先检索
/// </summary>
/// <param name="component"></param>
private static void DepthFirstSearch(Component component)
{
Console.WriteLine(component.Name);
List<Component> children = component.components;
if (children == null || children.Count == 0) return;
foreach (Component child in children)
{
DepthFirstSearch(child);
}
}
输出结构如下图
安全式
抽象组件类(Component)
/// <summary>
/// 抽象类-仅声明容器对象(Composite)与叶子对象(Leaf)共有的方法和属性
/// </summary>
public abstract class Component
{
/// <summary>
/// 定义该对象名称
/// </summary>
public string Name { get; set; }
private List<Component> _components;
/// <summary>
/// 用来保存所有子节点对象
/// </summary>
public List<Component> components
{
get { return _components; }
set { _components = value; }
}
public Component(string name)
{
this.Name = name;
components = new List<Component>();
}
/// <summary>
/// 打开方法
/// </summary>
public abstract void Open();
}
容器组件类(抽象与实现)(Composite)
/// <summary>
/// 容器抽象对象。这里代表文件夹对象
/// </summary>
public abstract class IComposite : Component
{
protected IComposite(string name) : base(name)
{
}
/// <summary>
/// 下一页
/// </summary>
public abstract void Next();
/// <summary>
/// 上一页
/// </summary>
public abstract void Up();
/// <summary>
/// 添加
/// </summary>
public abstract void Add(Component component);
/// <summary>
/// 移除
/// </summary>
public abstract void Remove(Component component);
}
/// <summary>
/// 容器实现对象-层级容积。这里代表文件夹对象
/// </summary>
public class Composite : IComposite
{
public Composite(string name) : base(name)
{ }
public override void Add(Component component)
{
Console.WriteLine($"{Name}添加了{component.Name}");
components.Add(component);
}
public override void Next()
{
Console.WriteLine("下一节");
}
public override void Open()
{
Console.WriteLine("打开文件夹");
}
public override void Remove(Component component)
{
Console.WriteLine($"{Name}移除了{component.Name}");
components.Remove(component);
}
public override void Up()
{
Console.WriteLine("上一节");
}
}
节点组件类(抽象与实现)(Leaf)
/// <summary>
/// 叶子抽象对象
/// </summary>
public abstract class ILeaf : Component
{
protected ILeaf(string name) : base(name)
{
}
}
/// <summary>
/// 叶子实现对象-最终节点代表各种类型的文件
/// </summary>
public class Leaf : ILeaf
{
public Leaf(string name) : base(name)
{
}
public override void Open()
{
Console.WriteLine("打开文件");
}
}
客户端使用
private void WTBtn_Click(object sender, EventArgs e)
{
IComposite composite1 = new Composite("一级文件夹");
Console.WriteLine("创建一个一级文件夹");
IComposite composite2 = new Composite("二级文件夹");
Console.WriteLine("创建一个二级文件夹");
IComposite composite3 = new Composite("三级文件夹");
Console.WriteLine("创建一个三级文件夹");
composite2.Add(composite3);//先将三级文件夹装到二级文件夹下
composite1.Add(composite2);//再将二级文件夹装到一级文件夹下
ILeaf leaf1 = new Leaf("一个文档");
Console.WriteLine("创建一个一个文档");
ILeaf leaf2 = new Leaf("一个表格");
Console.WriteLine("创建一个一个表格");
composite2.Add(leaf1);//将文档装到二级文件夹
composite3.Add(leaf2);//将表格装到三级文件夹
Console.WriteLine("-----------输出一级文件夹下内容--------------");
composite1.components.ForEach(c =>
{
Console.WriteLine(c.Name);
});
Console.WriteLine("-----------输出二级文件夹下内容--------------");
composite2.components.ForEach(c => {
Console.WriteLine(c.Name);
});
Console.WriteLine("-----------输出三级文件夹下内容--------------");
composite3.components.ForEach(c => {
Console.WriteLine(c.Name);
});
Console.WriteLine("-----------展示一级文件夹深度检索内容--------------");
DepthFirstSearch(composite1);
Console.WriteLine("-----------展示二级文件夹深度检索内容--------------");
DepthFirstSearch(composite2);
}
/// <summary>
/// 广度优先检索
/// </summary>
/// <param name="component"></param>
private static void BreadthFirstSearch(Component component)
{
Queue<Component> q = new Queue<Component>();
q.Enqueue(component);
Console.WriteLine(component.Name);
while (q.Count > 0)
{
Component temp = q.Dequeue();
List<Component> children = temp.components;
foreach (Component child in children)
{
Console.WriteLine(child.Name);
q.Enqueue(child);
}
}
}
/// <summary>
/// 深度优先检索
/// </summary>
/// <param name="component"></param>
private static void DepthFirstSearch(Component component)
{
Console.WriteLine(component.Name);
List<Component> children = component.components;
if (children == null || children.Count == 0) return;
foreach (Component child in children)
{
DepthFirstSearch(child);
}
}
END---------------------------------------------------------------------------------------------------------------------