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 ...
随机推荐
- JavaScript进阶指南: DOM与BOM操作,从初学者到专家,一步也能登天一篇文章就足够了
DOM与BOM操作 复习链接: http://c.biancheng.net/view/9360.html 事件对象: https://www.runoob.com/jsref/dom-obj-eve ...
- Sa-Token 多账号认证:同时为系统的 Admin 账号和 User 账号提供鉴权操作
Sa-Token 是一个轻量级 java 权限认证框架,主要解决登录认证.权限认证.单点登录.OAuth2.微服务网关鉴权 等一系列权限相关问题. Gitee 开源地址:https://gitee.c ...
- K8S | Deployment应用编排
目录 一.背景 二.Deployment组件 1.简介 2.语法说明 三.基础用例 1.创建操作 2.查看信息 3.更新操作 4.删除操作 四.进阶用例 1.回滚操作 2.伸缩操作 3.暂停与恢复 五 ...
- 线程池shutdown引发TimeoutException
问题描述 分享一个发版过程服务报错问题,问题出现在每次发版,服务准备下线的时候,报错的位置是在将任务submit提交给线程池,使用Future.get()引发的TimeoutException,错误日 ...
- Linux 修改文本dos/unix 格式
使用 vi 命令打开文件,使用 :set ff 查看格式 如果显示dos,可使用 :set ff=unix 修改 pem 证书,使用 cat xx.key xx.crt DigiCertCA.crt ...
- 2021-7-6 VUE笔记
v-cloak:使用的display:none: 直到编译完成后开始显示: v-text和插值表达式,非必要响应式用v-text会比较好,使用插值表达式要加上v-cloak; v-html:不推荐使用 ...
- shell分析nginx日志的一些指令
前言 nginx日志格式默认 shell指令 查看有多少个IP访问: awk '{print $1}' log_file|sort|uniq|wc -l 查看某一个页面被访问的次数: grep &qu ...
- Hybrid App 技术路径带动性能的提升
说到 Hybrid App(混合应用)大家都不陌生,因为这种开发模式大行其道发展的这些年取代了很多原生和 Web 应用,为什么大家对这种「Native + HTML5」的开发模式额外偏爱呢? 因为一方 ...
- MySQL高级9-锁
一.简介 锁是计算机协调多个进程或线程并发访问某一资源的机制.在数据库中,除了传统的计算资源(CPU.RAM.i/O)的挣用以外,数据也是一种供许多用户共享的资源.如何保证数据并发访问的一致性,有效性 ...
- git pull 强制覆盖本地代码
使用git pull更新本地代码,报以下错误: 解决办法如下. 1.备份本地代码 备份,可以考虑直接复制一份项目保存 2.远程覆盖本地 远程覆盖本地容易出现远程和本地冲突的情况 解决办法如下: //1 ...