一、动机(Motivate)

在我们的操作系统中有文件夹的概念,文件夹可以包含文件夹,可以嵌套多层,最里面包含的是文件,这个概念和“俄罗斯套娃”很像。当然还有很多的例子,例如我们使用系统的时候,会使用到“系统菜单”,这个东西是树形结构。这些例子包含的这些东西或者说是对象,可以分为两类,一类是:容器对象,可以包含其他的子对象;另一类是:叶子对象,这类对象是不能在包含其他对象的对象了。在软件设计中,我们该怎么处理这种情况呢?是每类对象分别对待,还是提供一个统一的操作方式呢。组合模式给我们提供了一种解决此类问题的一个途径。

客户代码过多地依赖于对象容器(对象容器是对象的容器,细细评味)复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性、扩展性等方面的弊端。如何将“客户代码与复杂的对象容器结构”解耦?如何让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?

二、意图(Intent)

将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。        ——  《设计模式》GoF

三、结构图(Structure)

四、模式的组成

组合模式中涉及到三个角色:
(1)、抽象构件角色(Component):这是一个抽象角色,它给参加组合的对象定义出了公共的接口及默认行为,可以用来管理所有的子对象(在透明式的组合模式是这样的)。在安全式的组合模式里,构件角色并不定义出管理子对象的方法,这一定义由树枝结构对象给出。
(2)、树叶构件角色(Leaf):树叶对象是没有下级子对象的对象,定义出参加组合的原始对象的行为。(原始对象的行为可以理解为没有容器对象管理子对象的方法,或者 【原始对象行为】+【管理子对象的行为(Add,Remove等)】=面对客户代码的接口行为集合)
(3)、树枝构件角色(Composite):代表参加组合的有下级子对象的对象,树枝对象给出所有管理子对象的方法实现,如Add、Remove等。
   组合模式实现的最关键的地方是——简单对象和复合对象必须实现相同的接口。这就是组合模式能够将组合对象和简单对象进行一致处理的原因。

五、组合模式的具体代码实现

组合模式有两种实现方式,一种是:透明式的组合模式,另外一种是:安全式的组合模式

1、透明式的组合模式

指“抽象构件角色”定义的接口行为集合包含两个部分,一部分是叶子对象本身所包含的行为(比如Operation),另外一部分是容器对象本身所包含的管理子对象的行为(Add,Remove)。这个抽象构件必须同时包含这两类对象所有的行为,客户端代码才会透明的使用,无论调用容器对象还是叶子对象,接口方法都是一样的,这就是透明,针对客户端代码的透明。

/// <summary>
/// 该抽象类就是文件夹抽象接口的定义,该类型就相当于是抽象构件Component类型
/// </summary>
public abstract class Folder
{
public abstract void Add(Folder folder);//增加文件夹或文件
public abstract void Remove(Folder folder);//删除文件夹或者文件
public abstract void Open(); //打开文件或者文件夹--该操作相当于Component类型的Operation方法
} /// <summary>
/// 该Word文档类就是叶子构件的定义,该类型就相当于是Leaf类型,不能在包含子对象
/// </summary>
public sealed class Word : Folder
{
public override void Add(Folder folder)//增加文件夹或文件
{
throw new Exception("Word文档不具有该功能");
} public override void Remove(Folder folder)//删除文件夹或者文件
{
throw new Exception("Word文档不具有该功能");
} public override void Open()//打开文件--该操作相当于Component类型的Operation方法
{
Console.WriteLine("打开Word文档,开始进行编辑");
}
} /// <summary>
/// SonFolder类型就是树枝构件,由于我们使用的是“透明式”,所以Add,Remove都是从Folder类型继承下来的
/// </summary>
public class SonFolder : Folder
{
public override void Add(Folder folder)//增加文件夹或文件
{
Console.WriteLine("文件或者文件夹已经增加成功");
} public override void Remove(Folder folder)//删除文件夹或者文件
{
Console.WriteLine("文件或者文件夹已经删除成功");
} public override void Open()//打开文件夹--该操作相当于Component类型的Operation方法
{
Console.WriteLine("已经打开当前文件夹");
}
} public class Program
{
static void Main()
{
Folder myword = new Word();
myword.Open();//打开文件,处理文件
myword.Add(new SonFolder());//抛出异常
myword.Remove(new SonFolder());//抛出异常 Folder myfolder = new SonFolder();
myfolder.Open();//打开文件夹
myfolder.Add(new SonFolder());//成功增加文件或者文件夹
myfolder.Remove(new SonFolder());//成功删除文件或者文件夹
}
}

2、安全式的组合模式

指“抽象构件角色”只定义叶子对象的方法,确切的说这个抽象构件只定义两类对象共有的行为,然后容器对象的方法定义在“树枝构件角色”上,这样叶子对象有叶子对象的方法,容器对象有容器对象的方法,这样责任很明确,当然调用肯定不会抛出异常了。

/// <summary>
/// 该抽象类就是文件夹抽象接口的定义,该类型就相当于是抽象构件Component类型
/// </summary>
public abstract class Folder //该类型少了容器对象管理子对象的方法的定义,换了地方,在树枝构件也就是SonFolder类型
{
public abstract void Open(); //打开文件或者文件夹--该操作相当于Component类型的Operation方法
} /// <summary>
/// 该Word文档类就是叶子构件的定义,该类型就相当于是Leaf类型,不能在包含子对象
/// </summary>
public sealed class Word : Folder //这类型现在很干净
{
public override void Open() //打开文件--该操作相当于Component类型的Operation方法
{
Console.WriteLine("打开Word文档,开始进行编辑");
}
} /// <summary>
/// SonFolder类型就是树枝构件,现在由于我们使用的是“安全式”,所以Add,Remove都是从此处开始定义的
/// </summary>
public abstract class SonFolder : Folder //这里可以是抽象接口,可以自己根据自己的情况而定
{
public abstract void Add(Folder folder); //增加文件夹或文件
public abstract void Remove(Folder folder); //删除文件夹或者文件
public override void Open()//打开文件夹--该操作相当于Component类型的Operation方法
{
Console.WriteLine("已经打开当前文件夹");
}
} /// <summary>
/// NextFolder类型就是树枝构件的实现类
/// </summary>
public sealed class NextFolder : SonFolder
{
public override void Add(Folder folder)//增加文件夹或文件
{
Console.WriteLine("文件或者文件夹已经增加成功");
} public override void Remove(Folder folder) //删除文件夹或者文件
{
Console.WriteLine("文件或者文件夹已经删除成功");
} public override void Open()//打开文件夹--该操作相当于Component类型的Operation方法
{
Console.WriteLine("已经打开当前文件夹");
}
} public class Program
{
static void Main()
{
Folder myword = new Word();//这是安全的组合模式
myword.Open();//打开文件,处理文件
Folder myfolder = new NextFolder();
myfolder.Open();//打开文件夹 //此处要是用增加和删除功能,需要转型的操作,否则不能使用
((SonFolder)myfolder).Add(new NextFolder());//成功增加文件或者文件夹
((SonFolder)myfolder).Remove(new NextFolder());//成功删除文件或者文件夹
}
}

六、组合模式的实现要点:

1、Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致地处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。
    2、将“客户代码与复杂的对象容器结构”解耦是Composite模式的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的复杂内部实现结构——发生依赖关系,从而更能“应对变化”。
    3、Composite模式中,是将“Add和Remove等和对象容器相关的方法”定义在“表示抽象对象的Component类”中,还是将其定义在“表示对象容器的Composite类”中,是一个关乎“透明性”和“安全性”的两难问题,需要仔细权衡。这里有可能违背面向对象的“单一职责原则”,但是对于这种特殊结构,这又是必须付出的代价。ASP.Net控件的实现在这方面为我们提供了一个很好的示范。
    4、Composite模式在具体实现中,可以让父对象中的子对象反向追朔;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率。

组合模式的优点:

(1)、组合模式使得客户端代码可以一致地处理对象和对象容器,无需关系处理的单个对象,还是组合的对象容器。
          (2)、将”客户代码与复杂的对象容器结构“解耦。
          (3)、可以更容易地往组合对象中加入新的构件。

组合模式的缺点:

(1)、使得设计更加复杂。客户端需要花更多时间理清类之间的层次关系。(这个是几乎所有设计模式所面临的问题)。

在以下情况下应该考虑使用组合模式:

(1)、需要表示一个对象整体或部分的层次结构。
           (2)、希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

七、.NET 中组合模式的实现

其实组合模式在FCL里面运用还是很多的,不知道大家是不是有所感觉,这个模式大多数是运用在控件上或者是和界面操作、展示相关的操作上。这个模式在.NET 中最典型的应用就是应用与WinForms和Web的开发中,在.NET类库中,都为这两个平台提供了很多现有的控件,然而System.Windows.Forms.dll中System.Windows.Forms.Control类就应用了组合模式,因为控件包括Label、TextBox等这样的简单控件,这些控件可以理解为叶子对象,同时也包括GroupBox、DataGrid这样复合的控件或者叫容器控件,每个控件都需要调用OnPaint方法来进行控件显示,为了表示这种对象之间整体与部分的层次结构,微软把Control类的实现应用了组合模式(确切地说应用了透明式的组合模式)。

结构型模式(四) 组合模式(Composite)的更多相关文章

  1. 结构型设计模式之组合模式(Composite)

    结构 意图 将对象组合成树形结构以表示“部分-整体”的层次结构.C o m p o s i t e 使得用户对单个对象和组合对象的使用具有一致性. 适用性 你想表示对象的部分-整体层次结构. 你希望用 ...

  2. 设计模式GOF23(结构型模式:代理模式,适配模式,桥接模式,组合模式,装饰模式,外观模式,享元模式)

    结构型模式: – 分类: • 适配器模式.代理模式.桥接模式.装饰模式.组合模式.外观模式.享元模式 – 核心作用:是从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题.   结构 ...

  3. Java计模模式之六 ----- 组合模式和过滤器模式

    前言 在上一篇中我们学习了结构型模式的外观模式和装饰器模式.本篇则来学习下组合模式和过滤器模式. 组合模式 简介 组合模式是用于把一组相似的对象当作一个单一的对象.组合模式依据树形结构来组合对象,用来 ...

  4. 迭代器模式和组合模式(head first设计模式——8)

    把迭代器模式和组合模式放在同一篇的原因是其联系比较紧密. 一.迭代器模式 1.1迭代器模式定义 迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而不是暴露其内部的表示. 这个模式提供了一种方法 ...

  5. Java设计模式(8)——结构型模式之组合模式(Composite)

    一.概述 定义 将对象以树形结构组织起来,以达成“部分-整体” 的层次结构,使得客户端对单个对象和组合对象的使用具有一致性. 简图 角色——对应上图中顶点为Component,左边为Leaf,右边为C ...

  6. GoF23种设计模式之结构型模式之组合模式

    一.概述 将对象组合成树型结构以表示“部分--整体”的层次关系.组合模式使得用户对单个对象和组合对象的使用具有一致性. 二.适用性 1.你想表示对象的部分--整体层次结构的时候. 2.你希望用户忽略组 ...

  7. [19/04/26-星期五] GOF23_结构型模式(桥接模式、组合模式)

    一.桥接模式(bridge) 场景:商城系统中常见的商品分类,以电脑为例,首先想到使用多层继承结构. —— 台式机(联想台式机.戴尔台式机.神舟台式机) 电脑    ——笔记本(联想笔记本.戴尔笔记本 ...

  8. 设计模式---数据结构模式之组合模式(Composite)

    前提:数据结构模式 常常有一些组建在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,将极大的破坏组件的复用.这时候,将这些数据结构封装在内部,在外部提供统一的接口,来实现与特定数据结构无 ...

  9. 设计模式16---设计模式之组合模式(Composite)(行为型)

    1.场景模拟 使用软件模拟大树的根节点和树枝节点和叶子节点 抽象为两类,容器节点和叶子节点 2.不用模式的解决方案 package demo14.composite.example1; import ...

随机推荐

  1. 【转】Fuel-Openstack的搭建(一)

    原文链接:https://blog.csdn.net/qq_35180983/article/details/82181496 2.1安装前的准备操作: 首先,我们需要准备以下操作: 2.1.1 下载 ...

  2. 数据结构-二叉树的遍历实现笔记C++

    二叉树的遍历实现,可以用递归的方法也可以用非递归的方法.非递归的方法可以借助栈(前序遍历,中序遍历,后序遍历),也可以借助队列(层次遍历).本次笔记只使用了递归的方法来进行前序遍历,中序遍历,后序遍历 ...

  3. 深度学习-语言处理特征提取 Word2Vec笔记

    Word2Vec的主要目的适用于词的特征提取,然后我们就可以用LSTM等神经网络对这些特征进行训练. 由于机器学习无法直接对文本信息进行有效的处理,机器学习只对数字,向量,多维数组敏感,所以在进行文本 ...

  4. gentoo emerge简单用法

    包管理工具 包的命名 gentoo中每个包名包含两部分,组名与包名,如:dev-db/redis,dev-db是组名,redis是包名或者称库名. 安装搜索时需要指定包的名称,一般不需要输入组名, 但 ...

  5. kali_Airmon-ng第一次渗透测试

    再看了一些资料之后,决定自己整理一下进行第一次测试,测试目标,自己宿舍的WIFI.教程仅供学习参考 断开kali连接的wifi,并检查网卡状态 airmon-ng 开启无线网卡的监控模式 airmon ...

  6. Gym 102055B Balance of the Force

    大意: $n$个骑士, 第$i$个骑士若加入光明阵营, 那么能力值$L_i$, 加入黑暗阵营, 能力值$D_i$. 给定$m$个限制$(u_i,v_i)$, 表示$u_i,v_i$不能在同一阵营. 求 ...

  7. c# webapi 过滤器token、sign认证、访问日志

    1.token认证 服务端登录成功后分配token字符串.记录缓存服务器,可设置有效期 var token = Guid.NewGuid().ToString().Replace("-&qu ...

  8. FreeMarker 教程整理

    Freemarker新手教程 http://blog.csdn.net/qq_23994787/article/details/77506980   FreeMarker教程整理 http://blo ...

  9. ASP.NET Core 入门(2)(WebApi接口请求日志 Request和Response)

    以前 .NET Framework WebApi 记录接口访问日志,一般是通过Filter的方式进行拦截,通过重写ActionFilterAttribute的OnActionExecuting实现拦截 ...

  10. JavaScript HTML DOM元素节点常用操作接口

    在文档对象模型 (DOM) 中,每个节点都是一个对象.DOM 节点有三个重要的属性 : 1. nodeName : 节点的名称 2. nodeValue :节点的值 3. nodeType :节点的类 ...