c#组合模式详解
基础介绍:
组合模式用于表示部分-整体的层次结构。适用于希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象的情况。
顾名思义,什么叫部分-整体,比如常见的前端UI,一个DIV标签中可以存在多个A标签、P标签、DIV标签等等。
相较于DIV这个容器整体而言,其中所含的A标签、P标签甚至是DIV标签都是单个的部分。
而显示的时候却是一视同仁,不分部分还是整体。
这就是典型的组合模式。
再比如WinForms应用程序中,Label、TextBox等这样简单的控件,可以理解为节点对象,它们中无法再插入其他控件,它们就是最小的。
而比如GroupBox、DataGrid这样由多个简单控件组成的复合控件或者容器,就可以理解为容器对象,它们中可以再插入其他的节点对象,甚至是再插入其他容器对象。
但不管是Label这种节点对象还是DataGrid这种容器对象,想要显示的话都需要执行OnPaint方法。
为了表示这种对象之间整体与部分的层次结构,System.Windows.Forms.Control类就是应用了这种组合模式。
这样就可以简单的把组合模式分为三个部分:
- 抽象组件类(Component):它可以是接口或抽象类,为节点组件和容器组件对象声明接口,在该类中包含共有行为的声明。在抽象组件类中,定义了访问及管理它的子组件的方法。
- 节点组件类(Leaf):节点对象为最小组件(可以理解为树叶),并继承自抽象组件类,实现其共有声明和方法。
- 容器组件类(Composite):容器对象可以包含无数节点对象和无数容器组件(可以理解为树枝,可以有无数树叶或者分支),容器对象需要实现管理子对象的方法,如Add、Remove等。
应用场景:
当发现需求中是体现部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑使用组合模式了。
UI的一系列控件就是使用了组合模式,整体和部分可以被一致对待。
组合模式有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
以下情况下适用Composite模式:
1.对象的部分-整体层次结构。
2.忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象。
创建方式:
组合模式实现的最关键的地方是——简单对象和复合对象必须实现相同的接口。这就是组合模式能够将组合对象和简单对象进行一致处理的原因。
组合模式有两种实现方式,一种是:透明式的组合模式,另外一种是:安全式的组合模式。
透明方式————————————————
Leaf叶类中也有Add 与 Remove方法,这种方式叫透明方式。
也就是说在Component中声明所有用来管理子对象的方法,其中包括Add、Remove等。
这样实现Component接口的所有子类都具备了Add与Remove。
这样做的好处是叶节点和枝节点对于外界没有区别,它们具有一致的行为接口。
但问题也很明显,因为Leaf类本身不具备Add、Remove方法的功能,其实现是没有意义的。
安全方式————————————————
  在Component接口中不去声明Add与Remove方法,那么子类Leaf也就不用必须实现它们,而在Composite类中声明所有用来管理子类对象的方法。
  
  
以文档管理器为例,文件夹为Composite,各类文档为Leaf。
- 透明方式 - 1.抽象类 - 1 /// <summary> 
 2 /// 抽象组件类(Component)
 3 /// </summary>
 4 public abstract class DocumentComponent
 5 {
 6 public string Name { get; set; }
 7 protected List<DocumentComponent> mChildren;
 8 public List<DocumentComponent> Children
 9 {
 10 get { return mChildren; }
 11 }
 12 public DocumentComponent(string name)
 13 {
 14 this.Name = name;
 15 mChildren = new List<DocumentComponent>();
 16 }
 17
 18
 19 public abstract void AddChild(DocumentComponent document);
 20
 21 public abstract void RemoveChild(DocumentComponent document);
 22
 23 public abstract void Show();
 24 }- 接口或抽象类,为节点组件和容器组件对象声明接口,在该类中包含共有行为的声明。 - 在抽象组件类中,定义了访问及管理它的子组件的方法。 - 本实例中Show为节点和容器组件共有方法,AddChild和RemoveChild为容器组件方法。 - 本类主要是为了让节点类和容器类进行继承方便统一管理。 - 2.节点组件类 - 1 /// <summary> 
 2 /// 节点组件类(Leaf),各类文档,每类型可以添加一个对应类。
 3 /// </summary>
 4 public sealed class Word : DocumentComponent
 5 {
 6 public Word(string name)
 7 : base(name)
 8 { }
 9 public override void AddChild(DocumentComponent document)
 10 {
 11 throw new Exception("节点类不支持");
 12 }
 13
 14 public override void RemoveChild(DocumentComponent document)
 15 {
 16 throw new Exception("节点类不支持");
 17 }
 18
 19 public override void Show()
 20 {
 21 Console.WriteLine("这是一篇word文档:" + Name);
 22 }
 23 }- 节点对象为最小组件(可以理解为树叶),并继承自抽象组件类,实现show方法。 - AddChild和RemoveChild为容器组件方法,在节点类中抛出异常即可。 - 该类是最小单位,没有子节点。 - 本类一个word文档对象,如果有多个类型的文档,可以声明多个类。 - 3.容器组件类 - 1 /// <summary> 
 2 /// 容器组件类(Composite),文件夹
 3 /// </summary>
 4 public class Folder : DocumentComponent
 5 {
 6 public Folder(string name)
 7 : base(name)
 8 { }
 9 public override void AddChild(DocumentComponent document)
 10 {
 11 mChildren.Add(document);
 12 Console.WriteLine("文档或文件夹增加成功");
 13 }
 14 public override void RemoveChild(DocumentComponent document)
 15 {
 16 mChildren.Remove(document);
 17 Console.WriteLine("文档或文件夹删除成功");
 18 }
 19 public override void Show()
 20 {
 21 Console.WriteLine("这是一个文件夹:" + Name);
 22 }
 23 }- 容器对象可以包含无数节点对象和无数容器组件(可以理解为树枝,可以有无数树叶或者分支),容器对象需要实现管理子对象的方法,如AddChild、RemoveChild等。 - 本类是一个文件夹对象。 - 4.客户端 - 1 /// <summary> 
 2 /// 客户端
 3 /// </summary>
 4 class Client
 5 {
 6 /// <summary>
 7 /// 广度优先检索
 8 /// </summary>
 9 /// <param name="component"></param>
 10 private static void BreadthFirstSearch(DocumentComponent component)
 11 {
 12 Queue<DocumentComponent> q = new Queue<DocumentComponent>();
 13 q.Enqueue(component);
 14 Console.WriteLine(component.Name);
 15 while (q.Count > 0)
 16 {
 17 DocumentComponent temp = q.Dequeue();
 18 List<DocumentComponent> children = temp.Children;
 19 foreach (DocumentComponent child in children)
 20 {
 21 Console.WriteLine(child.Name);
 22 q.Enqueue(child);
 23 }
 24 }
 25 }
 26
 27 /// <summary>
 28 /// 深度优先检索
 29 /// </summary>
 30 /// <param name="component"></param>
 31 private static void DepthFirstSearch(DocumentComponent component)
 32 {
 33 Console.WriteLine(component.Name);
 34 List<DocumentComponent> children = component.Children;
 35 if (children == null || children.Count == 0) return;
 36 foreach (DocumentComponent child in children)
 37 {
 38 DepthFirstSearch(child);
 39 }
 40 }
 41
 42 static void Main(string[] args)
 43 {
 44 Console.WriteLine("创建三个目录:");
 45 Folder folder = new Folder("根目录");
 46 Folder folder1 = new Folder("子目录1");
 47 Folder folder2 = new Folder("子目录2");
 48
 49 Console.WriteLine("\r\n创建两个文档:");
 50 Word word1 = new Word("word文档1");
 51 Word word2 = new Word("word文档2");
 52
 53 Console.WriteLine("\r\n将子目录1添加到根目录下:");
 54 folder.AddChild(folder1);
 55 Console.WriteLine("\r\n将子目录2添加到子目录1下:");
 56 folder1.AddChild(folder2);
 57
 58 Console.WriteLine("\r\n将word文档1添加到子目录2下:");
 59 folder2.AddChild(word1);
 60 Console.WriteLine("\r\n将word文档2添加到根目录下:");
 61 folder.AddChild(word2);
 62
 63 Console.WriteLine("\r\n广度优先列表:");
 64 DepthFirstSearch(folder);
 65 Console.WriteLine("\r\n深度优先列表:");
 66 BreadthFirstSearch(folder);
 67
 68 Console.ReadKey();
 69 }
 70
 71
 72 } - 注:BreadthFirstSearch为广度优先检索,依次列出所有元素。DepthFirstSearch为深度优先检索,列举完一个文件夹后,返回根目录继续列举其他文件夹。 - 通过上述实例可以看出,文件夹可以创建N个子文件夹,但文档只能放在文件夹中,无法放在另一个文档中。 
- 安全方式 - 1 /// <summary> 
 2 /// 抽象组件类(Component)
 3 /// </summary>
 4 public abstract class DocumentComponent
 5 {
 6 public string Name { get; set; }
 7 protected List<DocumentComponent> mChildren;
 8 public List<DocumentComponent> Children
 9 {
 10 get { return mChildren; }
 11 }
 12 public DocumentComponent(string name)
 13 {
 14 this.Name = name;
 15 mChildren = new List<DocumentComponent>();
 16 }
 17
 18 public abstract void Show();
 19 }
 20
 21 /// <summary>
 22 /// 节点组件类(Leaf),各类文档,每类型可以添加一个对应类。
 23 /// </summary>
 24 public sealed class Word : DocumentComponent
 25 {
 26 public Word(string name)
 27 : base(name)
 28 { }
 29
 30 public override void Show()
 31 {
 32 Console.WriteLine("这是一篇word文档:" + Name);
 33 }
 34 }
 35
 36 /// <summary>
 37 /// 容器组件类(Composite),文件夹
 38 /// </summary>
 39 public class Folder : DocumentComponent
 40 {
 41 public Folder(string name)
 42 : base(name)
 43 { }
 44 public void AddChild(DocumentComponent document)
 45 {
 46 mChildren.Add(document);
 47 Console.WriteLine("文档或文件夹增加成功");
 48 }
 49 public void RemoveChild(DocumentComponent document)
 50 {
 51 mChildren.Remove(document);
 52 Console.WriteLine("文档或文件夹删除成功");
 53 }
 54 public override void Show()
 55 {
 56 Console.WriteLine("这是一个文件夹:" + Name);
 57 }
 58 }
 59
 60
 61 /// <summary>
 62 /// 客户端
 63 /// </summary>
 64 class Client
 65 {
 66 /// <summary>
 67 /// 广度优先检索
 68 /// </summary>
 69 /// <param name="component"></param>
 70 private static void BreadthFirstSearch(DocumentComponent component)
 71 {
 72 Queue<DocumentComponent> q = new Queue<DocumentComponent>();
 73 q.Enqueue(component);
 74 Console.WriteLine(component.Name);
 75 while (q.Count > 0)
 76 {
 77 DocumentComponent temp = q.Dequeue();
 78 List<DocumentComponent> children = temp.Children;
 79 foreach (DocumentComponent child in children)
 80 {
 81 Console.WriteLine(child.Name);
 82 q.Enqueue(child);
 83 }
 84 }
 85 }
 86
 87 /// <summary>
 88 /// 深度优先检索
 89 /// </summary>
 90 /// <param name="component"></param>
 91 private static void DepthFirstSearch(DocumentComponent component)
 92 {
 93 Console.WriteLine(component.Name);
 94 List<DocumentComponent> children = component.Children;
 95 if (children == null || children.Count == 0) return;
 96 foreach (DocumentComponent child in children)
 97 {
 98 DepthFirstSearch(child);
 99 }
 100 }
 101
 102 static void Main(string[] args)
 103 {
 104 Console.WriteLine("创建三个目录:");
 105 Folder folder = new Folder("根目录");
 106 Folder folder1 = new Folder("子目录1");
 107 Folder folder2 = new Folder("子目录2");
 108
 109 Console.WriteLine("\r\n创建两个文档:");
 110 Word word1 = new Word("word文档1");
 111 Word word2 = new Word("word文档2");
 112
 113 Console.WriteLine("\r\n将子目录1添加到根目录下:");
 114 folder.AddChild(folder1);
 115 Console.WriteLine("\r\n将子目录2添加到子目录1下:");
 116 folder1.AddChild(folder2);
 117
 118 Console.WriteLine("\r\n将word文档1添加到子目录2下:");
 119 folder2.AddChild(word1);
 120 Console.WriteLine("\r\n将word文档2添加到根目录下:");
 121 folder.AddChild(word2);
 122
 123 Console.WriteLine("\r\n广度优先列表:");
 124 DepthFirstSearch(folder);
 125 Console.WriteLine("\r\n深度优先列表:");
 126 BreadthFirstSearch(folder);
 127
 128 Console.ReadKey();
 129 }
 130
 131 }- 从上述实例中可以看出,安全模式其实就是把共有的方法放在抽象类的。 - 文件夹独有的方法放在容器类中,这样做保证了节点类就没有Add和Remove等无用方法。 
总结:
组合模式解耦了客户程序与复杂元素内部结构,从而使客户程序可以向处理简单元素一样来处理复杂元素。
c#组合模式详解的更多相关文章
- Extjs MVC开发模式详解
		Extjs MVC开发模式详解 在JS的开发过程中,大规模的JS脚本难以组织和维护,这一直是困扰前端开发人员的头等问题.Extjs为了解决这种问题,在Extjs 4.x版本中引入了MVC开发模式, ... 
- JavaScript严格模式详解
		转载自阮一峰的博客 Javascript 严格模式详解 作者: 阮一峰 一.概述 除了正常运行模式,ECMAscript 5添加了第二种运行模式:"严格模式"(strict m ... 
- HTTP协议头部与Keep-Alive模式详解
		HTTP协议头部与Keep-Alive模式详解 .什么是Keep-Alive模式? 我们知道HTTP协议采用“请求-应答”模式,当使用普通模式,即非KeepAlive模式时,每个请求/应答客户和服务器 ... 
- (" use strict")Javascript 严格模式详解
		Javascript 严格模式详解 转载别人的博客内容,浏览了一遍,没有全部吸收,先保存一下链接 http://www.ruanyifeng.com/blog/2013/01/javascript_s ... 
- Javascript设计模式之装饰者模式详解篇
		一.前言: 装饰者模式(Decorator Pattern):在不改变原类和继承的情况下动态扩展对象功能,通过包装一个对象来实现一个新的具有原对象相同接口的新的对象. 装饰者模式的特点: 1. 在不改 ... 
- HTTP协议Keep-Alive模式详解
		详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp22 HTTP协议Keep-Alive模式详解 1.什么是Keep-Aliv ... 
- Java开源生鲜电商平台-盈利模式详解(源码可下载)
		Java开源生鲜电商平台-盈利模式详解(源码可下载) 该平台提供一个联合买家与卖家的一个平台.(类似淘宝购物,这里指的是食材的购买.) 平台有以下的盈利模式:(类似的平台有美菜网,食材网等) 1. 订 ... 
- ext.js的mvc开发模式详解
		ext.js的mvc开发模式详解和环境配置 在JS的开发过程中,大规模的JS脚本难以组织和维护,这一直是困扰前端开发人员的头等问题.Extjs为了解决这种问题,在Extjs 4.x版本中引入了MVC开 ... 
- Docker Kubernetes  Service 网络服务代理模式详解
		Docker Kubernetes Service 网络服务代理模式详解 Service service是实现kubernetes网络通信的一个服务 主要功能:负载均衡.网络规则分布到具体pod 注 ... 
- ST MCU_GPIO的八种工作模式详解。
		补充: N.P型的区别,就是一个为正电压启动(NMOS),一个为负电压启动(PMOS) GPIO的八种工作模式详解 浮空输入_IN_FLOATING带上拉输入_IPU带下拉输入_IPD模拟输入_AIN ... 
随机推荐
- docker部署zabbix6.0及企业微信发送告警
			1 前言 1.1 实验背景 因zabbix 6.0新增许多新特性,为熟悉界面特意在本地部署一套简易版(未启用HA功能).原本想要在烧制了centos7.9系统树莓派上部署,一查之下armv7果然冷门, ... 
- 用python selenium提取网页中的所有<a>标签中的超级链接地址
			urls = driver.find_elements_by_xpath("//a") for url in urls: print(url.get_attribute(" ... 
- CoaXPress 2.0 FPGA HOST IP Core Linux Demo
			目录 Hello-FPGA CoaXPress 2.0 Host FPGA IP Core Linux Demo 4 1 说明 4 2 设备连接 7 3 VIVADO FPGA工程 7 4 调试说明 ... 
- 采用PCA算法&KMeans算法来实现用户对物品类别的喜好细分(菜篮子分析)(附带数据集下载)
			实现该项目的流程如下 """ 项目:用户对物品类别的喜好细分(菜篮子分析) 主算法:PCA降维算法 KMeans算法 总思路 1.导包 2.获取数据 3.数据处理 5.特 ... 
- 从浅入深了解.NET Core MVC 2.x全面教程
			一.基础 1.默认配置 使用Kestrel Web Server ASP.NET Core内置--跨平台 IIS集成 UseIIS() UseIISIntergration() Log IConfig ... 
- 简单搭建基本Prometheus监控系统
			前言 适用场景:新手入门:内网离线部署,只是需要了解服务器基本情况,不需要告警系统. 需要准备的安装包: grafana的docker包(先在线下载,然后docker save保存镜像.如果没有doc ... 
- Jmeter+Ant+Jenkins接口自动化测试平台
			一个完整的接口自动化测试平台需要支持接口的自动执行,自动生成测试报告,以及持续集成. Jmeter 支持接口的测试, Ant 支持自动构建,而 Jenkins 支持持续集成,所以三者组合在一起可以构成 ... 
- Python第三方库pydash功能介绍
			Python第三方库pydash功能介绍 本文来自ChatGPT的回答整理 demo部分都验证过ok 介绍 pydash 是一个 Python 库,用于提供类似于 JavaScript 库 lodas ... 
- Vue+SpringBoot项目分离部署踩坑记录
			昨天花了一晚上终于成功部署了个人网站,在这个过程中踩了很多坑,现在回顾总结记录一下,以免今后继续犯错误 前端:Vue 后端:SpringBoot 数据库:Mysql 一.前端 1.前端项目采用Ngin ... 
- 反汇编ARM程序的技术靠谱吗?——揭秘ARM架构二进制程序的反汇编技术现状
			 本文系原创,转载请说明出处 Please Subscribe Wechat Official Account:信安科研人,获取更多的原创安全资讯 参考发表在2020年软工顶会ISSTA的论文&l ... 
