基础介绍:

  组合模式用于表示部分-整体层次结构。适用于希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象的情况。

  顾名思义,什么叫部分-整体,比如常见的前端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.抽象类

     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个子文件夹,但文档只能放在文件夹中,无法放在另一个文档中。

  2. 安全方式

      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#组合模式详解的更多相关文章

  1. Extjs MVC开发模式详解

    Extjs MVC开发模式详解   在JS的开发过程中,大规模的JS脚本难以组织和维护,这一直是困扰前端开发人员的头等问题.Extjs为了解决这种问题,在Extjs 4.x版本中引入了MVC开发模式, ...

  2. JavaScript严格模式详解

    转载自阮一峰的博客 Javascript 严格模式详解   作者: 阮一峰 一.概述 除了正常运行模式,ECMAscript 5添加了第二种运行模式:"严格模式"(strict m ...

  3. HTTP协议头部与Keep-Alive模式详解

    HTTP协议头部与Keep-Alive模式详解 .什么是Keep-Alive模式? 我们知道HTTP协议采用“请求-应答”模式,当使用普通模式,即非KeepAlive模式时,每个请求/应答客户和服务器 ...

  4. (" use strict")Javascript 严格模式详解

    Javascript 严格模式详解 转载别人的博客内容,浏览了一遍,没有全部吸收,先保存一下链接 http://www.ruanyifeng.com/blog/2013/01/javascript_s ...

  5. Javascript设计模式之装饰者模式详解篇

    一.前言: 装饰者模式(Decorator Pattern):在不改变原类和继承的情况下动态扩展对象功能,通过包装一个对象来实现一个新的具有原对象相同接口的新的对象. 装饰者模式的特点: 1. 在不改 ...

  6. HTTP协议Keep-Alive模式详解

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp22 HTTP协议Keep-Alive模式详解 1.什么是Keep-Aliv ...

  7. Java开源生鲜电商平台-盈利模式详解(源码可下载)

    Java开源生鲜电商平台-盈利模式详解(源码可下载) 该平台提供一个联合买家与卖家的一个平台.(类似淘宝购物,这里指的是食材的购买.) 平台有以下的盈利模式:(类似的平台有美菜网,食材网等) 1. 订 ...

  8. ext.js的mvc开发模式详解

    ext.js的mvc开发模式详解和环境配置 在JS的开发过程中,大规模的JS脚本难以组织和维护,这一直是困扰前端开发人员的头等问题.Extjs为了解决这种问题,在Extjs 4.x版本中引入了MVC开 ...

  9. Docker Kubernetes Service 网络服务代理模式详解

    Docker Kubernetes  Service 网络服务代理模式详解 Service service是实现kubernetes网络通信的一个服务 主要功能:负载均衡.网络规则分布到具体pod 注 ...

  10. ST MCU_GPIO的八种工作模式详解。

    补充: N.P型的区别,就是一个为正电压启动(NMOS),一个为负电压启动(PMOS) GPIO的八种工作模式详解 浮空输入_IN_FLOATING带上拉输入_IPU带下拉输入_IPD模拟输入_AIN ...

随机推荐

  1. Centos7制作本地yum仓库,共享给局域网其他设备

    环境准备: 准备好安装好Centos7的虚机A(服务端)和虚机B(客户端) 配置两台虚机网络使其互通,关闭selinux和firewalld等限制 下载完整的ISO镜像(CentOS-7-x86_64 ...

  2. 第十六届全国大学生 信息安全竞赛创新实践能力赛wp

    这是我第一次参加ctf,有许多东西都还不会,感觉有一些题挺有趣的,多积累积累经验吧. crypto Sign_in_passwd 下发了一个叫flag的文件,用记事本打开发现是两行加密,第一行看着像b ...

  3. mysql 命令安装

    1.   mysql  下载安装好压缩文件,下面我们进入正题,少废话. 09:39:112023-08-05 先到 mysql 官方网站下载:https://dev.mysql.com/downloa ...

  4. 利用IPV6随时访问家中影音Jellyfin

    本文章主要记录通过ipv6实现家庭影音中心在互联网上的访问. 之前很多方案都是通过第三方进行内网穿透,实际体验不是很好.目前ipv6发展迅速,完全可以取代这种以ipv4为中心的内网资源外网访问的方式. ...

  5. 《CTFshow-Web入门》02. Web 11~20

    @ 目录 web11 题解 原理 web12 题解 web13 题解 web14 题解 web15 题解 web16 题解 原理 web17 题解 web18 题解 原理 web19 题解 web20 ...

  6. API接口的重要性

    API接口的重要性在现代软件开发中无可替代.以下是API接口的几个重要方面: 1. 实现系统集成:API接口允许不同应用程序之间实现数据共享和交流.通过API接口,不同的软件系统可以相互连接和协作,实 ...

  7. 为不断增长的Go生态系统扩展gopls

    原文在这里. 由 Robert Findley and Alan Donovan 发布于 2023年9月8日 今年夏天初,Go团队发布了gopls的v0.12版本,这是Go语言的语言服务器,它进行了核 ...

  8. EXE一机一码打包加密大师1.4.0更新-支持导出注册机

    EXE一机一码打包加密大师可以对EXE文件进行加密处理,可以让EXE支持一机一码功能,也可以支持静态打开密码功能, 方便开发人员想用户收费. 详细软件使用说明可以查看下面的说明文档: EXE一机一码打 ...

  9. MySQL-通过存储过程来添加和删除分区(List分区)

    1.背景原因 当前MySQL不支持在添加和删除分区时,使用IF NOT EXISTS和IF EXISTS.所以在执行调度任务时,直接通过ADD PARTITION和DROP PARTITION不可避免 ...

  10. Spring Cache + Caffeine实现本地缓存

    Caffeine简介 Caffeine是一个高性能,高命中率,低内存占用,near optimal 的本地缓存,简单来说它是 Guava Cache 的优化加强版 依赖 <dependency& ...