一、引出模式

在软件开发中,我们经常会遇到树型目录的功能,比如:管理商品的目录

如果让你来实现这个功能,你会怎么做呢?

我们先来分析分析:商品类别树上的节点有三类,根节点、树枝节点和叶子节点,在进一步根节点和树枝节点都是可以包含其他节点的,我们就叫它容器节点。这样,商品类别树就分为了容器节点和叶子节点,我们将它们分别实现成为对象。

代码示例:

class Program{static void Main(string[] args){//定义所有的组合对象Composite root = new Composite("服装");Composite c1 = new Composite("男装");Composite c2 = new Composite("女装");//定义所有的叶子对象Leaf leaf1 = new Leaf("衬衣");Leaf leaf2 = new Leaf("夹克");Leaf leaf3 = new Leaf("裙子");Leaf leaf4 = new Leaf("套装");//按照树的结构来组合组合对象和叶子对象
            root.AddComposite(c1);root.AddComposite(c2);c1.AddLeaf(leaf1);c1.AddLeaf(leaf2);c2.AddLeaf(leaf3);c2.AddLeaf(leaf4);//调用根对象的输出功能来输出整棵树root.PrintStruct("");Console.ReadKey();}}/// <summary>/// 叶子对象/// </summary>public class Leaf{/// <summary>/// 叶子对象的名字/// </summary>private string name = null;/// <summary>/// 构造方法,传入叶子对象的名字/// </summary>/// <param name="name">叶子对象的名字</param>public Leaf(string name){this.name = name;}/// <summary>/// 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字/// </summary>/// <param name="preStr">前缀,主要是按照层级拼接的空格,实现向后缩进</param>public void PrintStruct(string preStr){Console.WriteLine(preStr + "-" + name);}}/// <summary>/// 组合对象,可以包含其它组合对象或者叶子对象/// </summary>public class Composite{/// <summary>/// 用来记录包含的其它组合对象/// </summary>private List<Composite> childComposite = new List<Composite>();/// <summary>/// 用来记录包含的其它叶子对象/// </summary>private List<Leaf> childLeaf = new List<Leaf>();/// <summary>/// 组合对象的名字/// </summary>private string name = null;/// <summary>/// 构造方法,传入组合对象的名字/// </summary>/// <param name="name"></param>public Composite(string name){this.name = name;}/// <summary>/// 向组合对象加入被它包含的其它组合对象/// </summary>/// <param name="c"></param>public void AddComposite(Composite c){this.childComposite.Add(c);}/// <summary>/// 向组合对象加入被它包含的叶子对象/// </summary>/// <param name="leaf"></param>public void AddLeaf(Leaf leaf){this.childLeaf.Add(leaf);}/// <summary>/// 输出组合对象自身的结构/// </summary>/// <param name="preStr"></param>public void PrintStruct(String preStr){//先把自己输出去Console.WriteLine(preStr + "+" + this.name);//然后添加一个空格,表示向后缩进一个空格,输出自己包含的叶子对象preStr += " ";foreach (Leaf leaf in childLeaf){leaf.PrintStruct(preStr);}//输出当前对象的子对象了foreach (Composite c in childComposite){////递归输出每个子对象
                c.PrintStruct(preStr);}}}

功能上已经实现好了,但有何问题呢?

区分了组合对象和叶子对象,并进行有区别的对待,比如在Composite和Client里面,都需要区别对待这两种对象,这就是个问题。

对于这种具有整体与部分关系,并能组合成树型结构的对象结构,如何才能够以一个统一的方式来进行操作呢?

二、认识模式

1.模式定义

将对象组合成为属性结构以表示“整体-部分”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

2.解决思路

上述例子中,要区分组合对象和叶子对象,就是因为没有把组合对象和叶子对象统一起来。

组合模式通过引入一个抽象的组件对象,作为组合对象和叶子对象的父对象,这样就把组合对象和叶子对象统一起来,用户使用时,始终是在操作组件对象,而不用再区分是在操作组合对象还是叶子对象。

3.模式图示

Component:抽象的组件对象,为组合中的对象声明接口,让客户端可以通过这个接口来访问和管理整个对象结构,可以在里面为定义的功能提供缺省的实现。

Leaf:叶子节点对象,定义和实现叶子对象的行为,不再包含其它的子节点对象。

Composite:组合对象,通常会存储子组件,定义包含子组件的那些组件的行为,并实现在组件接口中定义的与子组件有关的操作。

Client:客户端,通过组件接口来操作组合结构里面的组件对象。

4.模式原型示例代码

class Program{static void Main(string[] args){//定义多个Composite对象Component root = new Composite();Component c1 = new Composite();Component c2 = new Composite();//定义多个叶子对象Component leaf1 = new Leaf();Component leaf2 = new Leaf();Component leaf3 = new Leaf();//组和成为树形的对象结构
            root.AddChild(c1);root.AddChild(c2);root.AddChild(leaf1);c1.AddChild(leaf2);c2.AddChild(leaf3);//操作Component对象Component o = root.GetChildren(1);Console.WriteLine(o);}/// <summary>/// 抽象的组件对象,为组合中的对象声明接口,实现接口的缺省行为/// </summary>public abstract class Component{/// <summary>/// 示意方法,子组件对象可能有的功能方法/// </summary>public abstract void SomeOperation();/// <summary>/// 向组合对象中加入组件对象 /// </summary>/// <param name="component"></param>public virtual void AddChild(Component component){}/// <summary>/// 从组合对象中移出某个组件对象/// </summary>/// <param name="component"></param>public virtual void RemoveChild(Component component){}/// <summary>/// 返回某个索引对应的组件对象/// </summary>/// <param name="index"></param>/// <returns></returns>public virtual Component GetChildren(int index){}}/// <summary>/// 叶子对象,叶子对象不再包含其它子对象/// </summary>public class Leaf : Component{/// <summary>/// 示意方法,叶子对象可能有自己的功能方法/// </summary>public override void SomeOperation(){// do something
            }}/// <summary>/// 组合对象,通常需要存储子对象,定义有子部件的部件行为,/// 并实现在Component里面定义的与子部件有关的操作/// </summary>public class Composite : Component{/// <summary>/// 用来存储组合对象中包含的子组件对象/// </summary>private List<Component> childComponents = null;/// <summary>/// 示意方法,通常在里面需要实现递归的调用/// </summary>public override void SomeOperation(){if (childComponents != null){foreach (Component c in childComponents){//递归的进行子组件相应方法的调用
                        c.SomeOperation();}}}public override void AddChild(Component component){//延迟初始化if (childComponents == null){childComponents = new List<Component>();}childComponents.Add(component);}public override void RemoveChild(Component component){if (childComponents != null){childComponents.Remove(component);}}public override Component GetChildren(int index){if (childComponents != null){if (index >= 0 && index < childComponents.Count){return childComponents[index];}}return null;}}}

5.商品分类目录实例代码

class Program{static void Main(string[] args){//定义所有的组合对象Component root = new Composite("服装");Component c1 = new Composite("男装");Component c2 = new Composite("女装");//定义所有的叶子对象Component leaf1 = new Leaf("衬衣");Component leaf2 = new Leaf("夹克");Component leaf3 = new Leaf("裙子");Component leaf4 = new Leaf("套装");//按照树的结构来组合组合对象和叶子对象
            root.AddChild(c1);root.AddChild(c2);c1.AddChild(leaf1);c1.AddChild(leaf2);c2.AddChild(leaf3);c2.AddChild(leaf4);//调用根对象的输出功能来输出整棵树root.PrintStruct("");Console.ReadKey();}public abstract class Component{public abstract void PrintStruct(string preStr);public virtual void AddChild(Component component){}public virtual void RemoveChild(Component component){}public virtual Component GetChildren(int index){return null;}}public class Leaf : Component{private string name = null;public Leaf(string name){this.name = name;}public override void PrintStruct(string preStr){Console.WriteLine(preStr + "-" + name);}}public class Composite : Component{private string name = null;private List<Component> childComponents = null;public Composite(string name){this.name = name;}public override void PrintStruct(string preStr){Console.WriteLine(preStr + "+" + name);if (childComponents != null){preStr += "  ";foreach (var c in childComponents){ c.PrintStruct(preStr);}}}public override void AddChild(Component component){if (childComponents == null){childComponents = new List<Component>();}childComponents.Add(component);}public override void RemoveChild(Component component){if (childComponents == null){childComponents = new List<Component>();}childComponents.Remove(component);}public override Component GetChildren(int index){if (childComponents != null){if (index > 0 && index < childComponents.Count){return childComponents[index];}}return null;}}}

三、理解模式

1.组合模式的目的

组合模式的目的是:让客户端不再区分操作的是组合对象还是叶子对象,而是以一种统一的方式来操作

实现这个目标的关键之处,是设计一个抽象的组件类,让它可以代表组合对象和叶子对象。

2.对象树

组合模式会组合出树型结构,组成这个树型结构所使用的多个组件对象,就自然形成的对象树。

所有可以使用对象树来描述或操作的功能,都可以考虑组合模式。比如读取XML,或对语句进行语法解析等。

3.组合模式中的递归

组合模式中的递归,是对象本身的递归,是对象的组合方式,从设计上来讲是递归关联,是对象关联关系的一种。

4.安全性和透明性

在组合模式中,把组件对象分为两种:一种是可以包含子组件Composite对象;另一种不能包含其他组件对象的叶子对象。

Composite对象就像是一个容器,可以包含其他的Composite对或叶子对象。有了容器,就要对容器进行维护和管理。

这就产生了这样一个问题:在组合模式的类层次结构中,到底哪一些类里面定义这些管理子组件的操作,是应该在Component中声明这些操作呢,还是在Composite中声明这些操作?

这就需要仔细思考,在不同的实现中,进行安全性和透明性的权衡选择。

这里所说的安全性是指:从客户使用组合模式上看是否更安全。如果是安全的,那么不会有发生误操作的可能,能访问的方法都是被支持的功能。

这里所说的透明性是指:从客户使用组合模式上,是否需要区分到底是组合对象还是叶子对象。如果是透明的,那就是不再区分,对于客户而言,都是组件对象,具体的类型对于客户而言是透明的,是客户无需要关心的。

透明性的实现

如果把管理子组件的操作定义在Component中,那么客户端只需要面对Component,而无需关心具体的组件类型,这种实现方式就是透明性的实现。事实上,前面示例的实现方式都是这种实现方式。

但是透明性的实现是以安全性为代价的,因为在Component中定义的一些方法,对于叶子对象来说是没有意义的,比如:增加、删除子组件对象。而客户不知道这些区别,对客户是透明的,因此客户可能会对叶子对象调用这种增加或删除子组件的方法,这样的操作是不安全的。

组合模式的透明性实现,通常的方式是:在Component中声明管理子组件的操作,并在Component中为这些方法提供缺省的实现,如果是有子对象不支持的功能,缺省的实现可以是抛出一个例外,来表示不支持这个功能。

安全性的实现

如果把管理子组件的操作定义在Composite中,那么客户在使用叶子对象的时候,就不会发生使用添加子组件或是删除子组件的操作了,因为压根就没有这样的功能,这种实现方式是安全的。

但是这样一来,客户端在使用的时候,就必须区分到底使用的是Composite对象,还是叶子对象,不同对象的功能是不一样的。也就是说,这种实现方式,对客户而言就不是透明的了。

5.组合模式的优缺点

定义了包含基本对象和组合对象的类层次结构
    在组合模式中,基本对象可以被组合成更复杂的组合对象,而组合对象又可以组合成更复杂的组合对象,可以不断地递归组合下去,从而构成一个统一的组合对象的类层次结构

统一了组合对象和叶子对象
    在组合模式中,可以把叶子对象当作特殊的组合对象看待,为它们定义统一的父类,从而把组合对象和叶子对象的行为统一起来

简化了客户端调用
    组合模式通过统一组合对象和叶子对象,使得客户端在使用它们的时候,就不需要再去区分它们,客户不关心使用的到底是什么类型的对象,这就大大简化了客户端的使用

更容易扩展
    由于客户端是统一的面对Component来操作,因此,新定义的Composite或Leaf子类能够很容易的与已有的结构一起工作,而客户端不需要为增添了新的组件类而改变

很难限制组合中的组件类型
    容易增加新的组件也会带来一些问题,比如很难限制组合中的组件类型。这在需要检测组件类型的时候,使得我们不能依靠编译期的类型约束来完成,必须在运行期间动态检测。

6.何时选用组合模式

建议在如下情况中,选用组合模式:

如果你想表示对象的部分-整体层次结构,可以选用组合模式,把整体和部分的操作统一起来,使得层次结构实现更简单,从外部来使用这个层次结构也简单

如果你希望统一的使用组合结构中的所有对象,可以选用组合模式,这正是组合模式提供的主要功能

7.组合模式的本质

组合模式的本质:统一叶子对象和组合对象。

组合模式通过把叶子对象当成特殊的组合对象看待,从而对叶子对象和组合对象一视同仁,统统当成了Component对象,有机的统一了叶子对象和组合对象。

正是因为统一了叶子对象和组合对象,在将对象构建成树形结构的时候,才不需要做区分,反正是组件对象里面包含其它的组件对象,如此递归下去;也才使得对于树形结构的操作变得简单,不管对象类型,统一操作。

转载于:https://www.cnblogs.com/zxj159/p/3484432.html

设计模式之组合模式(十四)相关推荐

  1. 了解23种设计模式之组合模式

    一,什么是组合模式 Composite 模式 也叫组合模式,是构造型的设计模式之一,通过递归手段来构造树形的对象结构,并可以通过一个对象来访问整个对象树 二,组合模式的结构 组合模式的角色和职责 Co ...

  2. 每天一个设计模式之组合模式

    作者按:<每天一个设计模式>旨在初步领会设计模式的精髓,目前采用javascript和python两种语言实现.诚然,每种设计模式都有多种实现方式,但此小册只记录最直截了当的实现方式 :) ...

  3. 【设计模式】组合模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )

    文章目录 一.组合模式简介 二.组合模式适用场景 三.组合模式优缺点 四.组合模式和访问者模式 五.组合模式代码示例 1.书籍和目录的抽象父类 2.书籍类 3.目录类 4.测试类 一.组合模式简介 组 ...

  4. 1、【设计模式】组合模式

    java设计模式之组合模式 [学习难度:★★★☆☆,使用频率:★★★★☆]  树形结构在软件中随处可见,例如操作系统中的目录结构.应用软件中的菜单.办公系统中的公司组织结构等等,如何运用面向对象的方式 ...

  5. 详解设计模式:组合模式

    组合模式(Composite Pattern),又叫部分整体模式,是 GoF 的 23 种设计模式中的一种结构型设计模式. 组合模式 是用于把一组相似的对象当作一个单一的对象.组合模式依据树形结构来组 ...

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

    结构型设计模式之组合模式 组合模式 应用场景 优缺点 主要角色 组合模式结构 分类 透明组合模式 创建抽象根节点 创建树枝节点 创建叶子节点 客户端调用 安全组合模式 创建抽象根节点 创建树枝节点 创 ...

  7. java设计模式之组合模式(树形层级)

    java设计模式之组合模式 学习难度:★★★☆☆,使用频率:★★★★☆]  树形结构在软件中随处可见,例如操作系统中的目录结构.应用软件中的菜单.办公系统中的公司组织结构等等,如何运用面向对象的方式来 ...

  8. Java设计模式之组合模式详解

    文章目录 详解Java设计模式之组合模式 案例引入 组合模式 定义 模式类图结构 相关角色 典型代码 案例分析 类图设计 实例代码 结果分析 JavaJDK中的组合模式 透明组合模式 安全组合模式 组 ...

  9. Java设计模式之组合模式(UML类图分析+代码详解)

    大家好,我是一名在算法之路上不断前进的小小程序猿!体会算法之美,领悟算法的智慧~ 希望各位博友走过路过可以给我点个免费的赞,你们的支持是我不断前进的动力!! 加油吧!未来可期!! 本文将介绍java设 ...

  10. 【Java设计模式】组合模式

    转自:  https://blog.csdn.net/qq_42322103/article/details/95457321 漫谈网站优化提速: https://blog.csdn.net/mete ...

最新文章

  1. python项目实践_Python 项目实践三(Web应用程序)第二篇
  2. NeurIPS 2021 | 港中文周博磊组:基于实例判别的数据高效生成模型
  3. My task - how is inline creation implemented
  4. 把Autofac玩的和java Spring一样6
  5. 使用乱序标签来控制HTML的输出效果
  6. 重温CLR(八 ) 泛型
  7. vue如何和PHP交互,VUE中如何使用Vue-resource完成交互
  8. static_cast,reinterpret_cast,const_cast,dynamic_cast:
  9. android-sdk环境变量配置
  10. 云优CMS企业网站管理系统 v2.2.2 分站版
  11. 开发者必看:Google Play应用上架流程(希望你不踩坑!)
  12. 剑指offer(41-50题)详解
  13. 手机浏览器 html编辑器,vue移动端富文本编辑器vue-html5-editor
  14. 网络编程中的EGAIN和EWOULDBLOCK
  15. 【欧几里得扩展欧几里得】
  16. 第一台计算机作文,精选电脑三年级作文6篇
  17. 小学英语教学c语言,小学英语老师常用的课堂操练游戏100例
  18. 2019 ArXiv之ReID:Hetero-Center Loss for Cross-Modality Person Re-Identification
  19. 记关于SaaS平台中应对多租户模式的设计
  20. CVE-2022-21882 分析POC

热门文章

  1. java反射 javabean_Java反射之模仿JavaBean接收表单参数.
  2. 数据结构和算法——中序线索化二叉树
  3. Java 中isEmpty和null、 的区别
  4. 对空进行判断需要注意什么?
  5. hive udaf_Hive 混合函数 UDTF UDF UDAF详解
  6. Java基础总结04-数组
  7. hibernate 基础方法(二)【相关配置详解】
  8. mysql (mariadb)数据库使用 mysqldump 备份全部数据库,并自动按当前时间名保存文件
  9. java集合了类面试题_一些集合类面试题,说不定你就会遇到
  10. 阶段3 1.Mybatis_07.Mybatis的连接池及事务_3 mybatis连接池的分类