C#设计模式学习笔记:(9)组合模式
本笔记摘抄自:https://www.cnblogs.com/PatrickLiu/p/7743118.html,记录一下学习过程以备后续查用。
一、引言
今天我们要讲结构型设计模式的第四个模式--组合模式。当我们谈到这个模式的时候,有一个物件和这个模式很像,那就是“俄罗斯套娃”。“俄罗斯套娃”是
大的瓷器娃娃里面装着一个小的瓷器娃娃,小的瓷器娃娃里面再装着更小的瓷器娃娃,直到最后一个不能再装更小的瓷器娃娃的那个瓷器娃娃为止。在我们
的操作系统中有文件夹的概念,文件夹可以包含文件夹并可以嵌套多层,这个和“俄罗斯套娃”很像。当然还有很多的例子,例如我们使用系统的时候,会使
用到“开始菜单”,这个菜单是树形结构。这些例子包含的东西或内容我们称之为对象,对象可以分为两类:一类是容器对象,可以包含其它的子对象;另一
类是:叶子对象,这类对象是不能再包含其它的对象。在软件设计中,我们该怎么处理这种情况呢?是每类对象分别对待,还是提供一个统一的操作方式呢?
组合模式给我们提供了一种解决此类问题的一个途径,接下来我们就好好地介绍一下“组合模式”吧。
二、组合模式介绍
组合模式:英文名称--Composite Pattern;分类--结构型。
2.1、动机(Motivate)
客户代码过多地依赖于对象容器(对象容器是对象的容器,细细评味)复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户
代码的频繁变化,带来了代码的维护性、扩展性等方面的弊端。如何将“客户代码与复杂的对象容器结构”解耦?如何让对象容器自己来实现自身的复杂结构,
从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?
2.2、意图(Intent)
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。——《设计模式》GoF
2.3、结构图(Structure)
2.4、模式的组成
组合模式中涉及到三个角色:
1)抽象构件角色(Component):这是一个抽象角色,它给参加组合的对象定义出了公共的接口及默认行为,可以用来管理所有的子对象(在透明式的组
合模式是这样的)。在安全式的组合模式里,构件角色并不定义出管理子对象的方法,这一定义由树枝结构对象给出。
2)树叶构件角色(Leaf):树叶对象是没有下级子对象的对象,定义出参加组合的原始对象的行为。(原始对象的行为可以理解为没有容器对象管理子对
象的方法,或者原始对象行为+管理子对象的行为(Add,Remove等)=面对客户代码的接口行为集合)
3)树枝构件角色(Composite):代表参加组合的有下级子对象的对象,树枝对象给出所有管理子对象的方法实现,如Add、Remove等。组合模式实现的
最关键的地方是--简单对象和复合对象必须实现相同的接口。这就是组合模式能够将组合对象和简单对象进行一致处理的原因。
2.5、组合模式的具体实现
组合模式有两种实现方式:一种是透明式的组合模式,另外一种是安全式的组合模式。在这里我就详细说一下何为“透明式”?何为“安全式”?所谓透明式是指
“抽象构件角色”定义的接口行为集合包含两个部分:一部分是叶子对象本身所包含的行为(比如Operation),另外一部分是容器对象本身所包含的管理子对象
的行为(Add,Remove)。这个抽象构件必须同时包含这两类对象所有的行为,客户端代码才会透明的使用,无论调用容器对象还是叶子对象,接口方法都是一样
的,这就是透明--针对客户端代码的透明,但是也有它自己的问题。叶子对象不会包含自己的子对象,为什么要有Add,Remove等类似方法呢?调用叶子对象这
样的方法可能(注意:我这里说的是可能,因为有些人会把这些方法实现为空,不做任何动作,当然也不会有异常抛出了,不要抬杠)会抛出异常,这样就不
安全了,然后人们就提出了“安全式的组合模式”。所谓安全式是指“抽象构件角色”只定义叶子对象的方法,确切的说这个抽象构件只定义两类对象共有的行为,
然后容器对象的方法定义在“树枝构件角色”上,这样叶子对象有叶子对象的方法,容器对象有容器对象的方法,这样责任很明确,当然调用肯定不会抛出异常了。
大家可以根据自己的情况自行选择是实现为“透视式”还是“安全式”,以下我们会针对这两种情况都进行实现。
2.5.1“透明式的组合模式”的具体实现
class Program
{
/// <summary>
/// 该抽象类是文件夹抽象接口的定义,该类型相当于抽象构件Component类型。
/// </summary>
public abstract class TransparentMoodFolder
{
//增加文件夹或文件
public abstract void Add(TransparentMoodFolder folder); //删除文件夹或者文件
public abstract void Remove(TransparentMoodFolder folder); //打开文件夹或者文件,该操作相当于Component类型的Operation方法。
public abstract void Open();
} /// <summary>
/// 该Word文档类就是叶子构件的定义,该类型相当于Leaf类型,不能再包含子对象。
/// </summary>
public sealed class TransparentMoodWord : TransparentMoodFolder
{
//增加文件夹或文件
public override void Add(TransparentMoodFolder folder)
{
throw new Exception("Word文档不具有该功能。");
} //删除文件夹或者文件
public override void Remove(TransparentMoodFolder folder)
{
throw new Exception("Word文档不具有该功能。");
} //打开文件--该操作相当于Component类型的Operation方法
public override void Open()
{
Console.WriteLine("打开Word文档,开始进行编辑。");
}
} /// <summary>
/// SonFolder类型就是树枝构件,由于我们使用的是“透明式”,所以Add、Remove都是从Folder类型继承下来的。
/// </summary>
public class TransparentMoodSonFolder : TransparentMoodFolder
{
//增加文件夹或文件
public override void Add(TransparentMoodFolder folder)
{
Console.WriteLine("文件夹或者文件增加成功。");
} //删除文件夹或者文件
public override void Remove(TransparentMoodFolder folder)
{
Console.WriteLine("文件夹或者文件删除成功。");
} //打开文件夹--该操作相当于Component类型的Operation方法
public override void Open()
{
Console.WriteLine("打开文件夹。");
}
} static void Main(string[] args)
{
#region 组合模式之透明式
TransparentMoodFolder myword = new TransparentMoodWord();
myword.Open(); //打开文件
//myword.Add(new TransparentMoodSonFolder()); //抛出异常
//myword.Remove(new TransparentMoodSonFolder()); //抛出异常 TransparentMoodFolder myfolder = new TransparentMoodSonFolder();
myfolder.Open(); //打开文件夹
myfolder.Add(new TransparentMoodSonFolder()); //增加文件夹或者文件
myfolder.Remove(new TransparentMoodSonFolder()); //删除文件夹或者文件 Console.Read();
#endregion
}
}
运行结果如下:
2.5.2“安全式的组合模式”的具体实现
class Program
{
/// <summary>
/// 该抽象类就是文件夹抽象接口的定义,该类型相当于抽象构件Component类型。
/// </summary>
public abstract class SafeMoodFolder //该类型少了容器对象管理子对象方法的定义,换在树枝构件也就是SonFolder类型中定义。
{
//打开文件夹或者文件--该操作相当于Component类型的Operation方法
public abstract void Open();
} /// <summary>
/// 该Word文档类就是叶子构件的定义,该类型相当于Leaf类型,不能再包含子对象。
/// </summary>
public sealed class SafeMoodWord : SafeMoodFolder //这类型现在很干净
{
//打开文件--该操作相当于Component类型的Operation方法
public override void Open()
{
Console.WriteLine("打开Word文档,开始进行编辑。");
}
} /// <summary>
/// SonFolder类型就是树枝构件,现在由于我们使用的是“安全式”,所以Add、Remove都是从此处开始定义的。
/// </summary>
public abstract class SafeMoodSonFolder : SafeMoodFolder //这里可以是抽象接口,可以自己根据自己的情况而定。
{
//增加文件夹或文件
public abstract void Add(SafeMoodFolder folder); //删除文件夹或者文件
public abstract void Remove(SafeMoodFolder folder); //打开文件夹--该操作相当于Component类型的Operation方法
public override void Open()
{
Console.WriteLine("打开文件夹。");
}
} /// <summary>
/// NextFolder类型就是树枝构件的实现类
/// </summary>
public sealed class SafeMoodNextFolder : SafeMoodSonFolder
{
//增加文件夹或文件
public override void Add(SafeMoodFolder folder)
{
Console.WriteLine("文件夹或者文件增加成功。");
} //删除文件夹或者文件
public override void Remove(SafeMoodFolder folder)
{
Console.WriteLine("文件夹或者文件删除成功。");
} //打开文件夹--该操作相当于Component类型的Operation方法
public override void Open()
{
Console.WriteLine("打开文件夹。");
}
} static void Main(string[] args)
{
#region 组合模式之安全式
SafeMoodFolder myword = new SafeMoodWord();
myword.Open(); //打开文件 SafeMoodFolder myfolder = new SafeMoodNextFolder();
myfolder.Open(); //打开文件夹
//此处要是用增加和删除功能,需要转换类型,否则不能使用。
((SafeMoodSonFolder)myfolder).Add(new SafeMoodNextFolder()); //成功增加文件或者文件夹
((SafeMoodSonFolder)myfolder).Remove(new SafeMoodNextFolder()); //成功删除文件或者文件夹 Console.Read();
#endregion
}
}
运行结果如下:
这两种模式都不是很难,请仔细体会实现关键点,最重要的是要理解模式的意图并结合结构图。
三、组合模式的实现要点
1)Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致地处理对象和对象容器,
无需关心处理的是单个的对象,还是组合的对象容器。
2)将“客户代码与复杂的对象容器结构”解耦是Composite模式的核心思想,解耦之后,客户代码将与纯粹的抽象接口而非对象容器的复杂内部实现结构发生
依赖关系,从而更能“应对变化”。
3)Composite模式中,是将“Add和Remove等和对象容器相关的方法”定义在“表示抽象对象的Component类”中,还是将其定义在“表示对象容器的Composite
类”中,是一个关乎“透明性”和“安全性”的两难问题,需要仔细权衡。这里有可能违背面向对象的“单一职责原则”,但是对于这种特殊结构,这又是必须付出的代价。
ASP.Net控件的实现在这方面为我们提供了一个很好的示范。
4)Composite模式在具体实现中,可以让父对象中的子对象反向追朔;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率。
3.1、组合模式的优点
1)组合模式使得客户端代码可以一致地处理对象和对象容器,无需关系处理的单个对象,还是组合的对象容器。
2)将”客户代码与复杂的对象容器结构“解耦。
3)可以更容易地往组合对象中加入新的构件。
3.2、组合模式的缺点
1)使得设计更加复杂。客户端需要花更多时间理清类之间的层次关系(这个是几乎所有设计模式所面临的问题)。
3.3、在以下情况下应该考虑使用组合模式
1)需要表示一个对象整体或部分的层次结构。
2)希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
四、.NET中组合模式的实现
其实组合模式在FCL里面运用还是很多的,不知道大家是不是有所感觉?这个模式大多数是运用在控件上或者是与界面操作、展示相关的操作上。
这个模式在.NET中最典型的应用就是WinForms和Web,在这两种平台的应用开发中,.NET类库提供了很多现有的控件,而System.Windows.Forms.dll中的
System.Windows.Forms.Control类就应用了组合模式,如控件Label、TextBox等控件,可以理解为叶子对象,而GroupBox、DataGrid等复合的控件可以称之为
容器控件,每个控件都需要调用OnPaint方法来进行控件显示,为了表示这种对象之间整体与部分的层次结构,微软在Control类的实现中应用了组合模式(确切
地说应用了透明式的组合模式)。
五、总结
模式这个东西就像“独孤九剑”,不要死记硬背,要多看看别人的、多写写代码、要理解场景和意图,多写多练吧,你就有可能成为一代大侠。模式学无止境,
我也是刚刚开始。
C#设计模式学习笔记:(9)组合模式的更多相关文章
- 设计模式学习笔记——Composite 组合模式
用于描述无限层级的复杂对象,类似于描述资源管理器,抽象出每一个层级的共同特点(文件夹和文件,展开事件) 以前描述一个对象,是将整个对象的全部数据都描述清楚,而组合模式通过在对象中定义自己,描述自己的下 ...
- 设计模式学习笔记--备忘录(Mamento)模式
写在模式学习之前 什么是设计模式:在我们进行程序设计时,逐渐形成了一些典型问题和问题的解决方式,这就是软件模式:每个模式描写叙述了一个在我们程序设计中常常发生的问题,以及该问题的解决方式:当我们碰到模 ...
- javascript设计模式学习之十——组合模式
一.组合模式定义及使用场景 组合模式将对象组合成树形结构,用以表示“部分—整体”的层次结构,除了用来表示树形结构之外,组合模式还可以利用对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性. ...
- 设计模式学习笔记——Bridge 桥接模式
先说一下我以前对桥接模式的理解:当每个类中都使用到了同样的属性或方法时,应该将他们单独抽象出来,变成这些类的属性和方法(避免重复造轮子),当时的感觉是和三层模型中的model有点单相似,也就是让mod ...
- 设计模式学习笔记-Adapter模式
Adapter模式,就是适配器模式,使两个原本没有关联的类结合一起使用. 平时我们会经常碰到这样的情况,有了两个现成的类,它们之间没有什么联系,但是我们现在既想用其中一个类的方法,同时也想用另外一个类 ...
- Java 设计模式学习笔记1——策略模式(Duck例子)
0.假设现有工程(Duck)中遇到为类添加功能的问题,如何设计类添加新的功能? 1.利用继承提供的Duck(鸭子)的行为会导致哪些缺点? (1)代码在多个子类中重复 (2)很多男知道所有鸭子的全部行为 ...
- Java-马士兵设计模式学习笔记-装饰者模式
Java装饰者模式简介 一.假设有一个Worker接口,它有一个doSomething方法,Plumber和Carpenter都实现了Worker接口,代码及关系如下: 1.Worker.java p ...
- 研磨设计模式学习笔记2--外观模式Facade
需求:客户端需要按照需求,执行一个操作,操作包括一个系统中的3个模块(根据配置选择是否全部执行). 外观模式优点: 客户端无需知道系统内部实现,,只需要写好配置文件,控制那些模块执行,简单易用. 外观 ...
- 设计模式学习笔记 1.factory 模式
Factory 模式 用户不关心工厂的具体类型,只知道这是一个工厂就行. 通过工厂的实现推迟到子类里面去来确定工厂的具体类型. 工厂的具体类型来确定生产的具体产品. 同时用户不关心这是一个什么样子的产 ...
- 设计模式学习笔记——Visitor 访问者模式
1.定义IVisitor接口,确定变化所涉及的方法 2.封装变化类.实现IVisitor接口 3.在实体类的变化方法中传入IVisitor接口,由接口确定使用哪一种变化来实现(封装变化) 4.在使用时 ...
随机推荐
- [ZJOI2008]树的统计(树链剖分)
[ZJOI2008]树的统计(luogu) Description 一棵树上有 n 个节点,编号分别为 1 到 n,每个节点都有一个权值 w.我们将以下面的形式来要求你对这棵树完成一些操作: I. C ...
- Python中__init__的用法和理解
在Python中定义类经常会用到__init__函数(方法),首先需要理解的是,两个下划线开头的函数是声明该属性为私有,不能在类的外部被使用或访问.而__init__函数(方法)支持带参数类的初始化, ...
- 保存网页内容到excel
from selenium import webdriverfrom time import sleepfrom selenium.common.exceptions import NoSuchEle ...
- python人脸识别
需要掌握知识python,opencv和机器学习一类的基础 过一段时间代码上传github,本人菜j一个,虽然是我自己谢的,也有好多不懂,或者我这就是错误方向 链接:https://pan.baidu ...
- Arduino系列之pwm控制LED灯(呼吸灯)
下面我将写出最简单控制呼吸灯的方法 void setup() // { pinMode(12,OUTPUT); ...
- (数据科学学习手札75)基于geopandas的空间数据分析——坐标参考系篇
本文对应代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 在上一篇文章中我们对geopandas中的数据结 ...
- Android View如何获取焦点
Android新启动Activity,dialog或者其他窗体中中包含EditText, 新启动的activity的焦点默认在EditText上,这是android系统会弹出软键盘,挤压activit ...
- used in key specification without a key length
官方的解释: The error happens because MySQL can index only the first N chars of a BLOB or TEXT column. So ...
- C++ traits技法的一点理解
为了更好的理解traits技法.我们一步一步的深入.先从实际写代码的过程中我们遇到诸如下面伪码说起. template< typename T,typename B> void (T a, ...
- c语言小游戏-三子棋的完成
三子棋的实现 一.实现思路 1.初始化数组 三子棋是九宫格的格式,所以用二维数组接收数据.用‘O’代表电脑下的子,‘X’代表玩家下的子.未下子的时候初始化 ’ ‘(space).则二维数组为“char ...