博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
设计模式之组合模式(十四)
阅读量:6922 次
发布时间:2019-06-27

本文共 11936 字,大约阅读时间需要 39 分钟。

一、引出模式

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

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

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

代码示例:

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();        }    }    ///     /// 叶子对象    ///     public class Leaf    {        ///         /// 叶子对象的名字        ///         private string name = null;        ///         /// 构造方法,传入叶子对象的名字        ///         /// 叶子对象的名字        public Leaf(string name)        {            this.name = name;        }        ///         /// 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字        ///         /// 前缀,主要是按照层级拼接的空格,实现向后缩进        public void PrintStruct(string preStr)        {            Console.WriteLine(preStr + "-" + name);        }    }    ///     /// 组合对象,可以包含其它组合对象或者叶子对象    ///     public class Composite    {        ///         /// 用来记录包含的其它组合对象        ///         private List
childComposite = new List
(); ///
/// 用来记录包含的其它叶子对象 /// private List
childLeaf = new List
(); ///
/// 组合对象的名字 /// private string name = null; ///
/// 构造方法,传入组合对象的名字 /// ///
public Composite(string name) { this.name = name; } ///
/// 向组合对象加入被它包含的其它组合对象 /// ///
public void AddComposite(Composite c) { this.childComposite.Add(c); } ///
/// 向组合对象加入被它包含的叶子对象 /// ///
public void AddLeaf(Leaf leaf) { this.childLeaf.Add(leaf); } ///
/// 输出组合对象自身的结构 /// ///
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); } } }

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

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

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

二、认识模式

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);        }        ///         /// 抽象的组件对象,为组合中的对象声明接口,实现接口的缺省行为        ///         public abstract class Component        {            ///             /// 示意方法,子组件对象可能有的功能方法            ///             public abstract void SomeOperation();            ///             /// 向组合对象中加入组件对象             ///             ///             public virtual void AddChild(Component component)            {            }            ///             /// 从组合对象中移出某个组件对象            ///             ///             public virtual void RemoveChild(Component component)            {            }            ///             /// 返回某个索引对应的组件对象            ///             ///             /// 
public virtual Component GetChildren(int index) { } } /// /// 叶子对象,叶子对象不再包含其它子对象 /// public class Leaf : Component { /// /// 示意方法,叶子对象可能有自己的功能方法 /// public override void SomeOperation() { // do something } } /// /// 组合对象,通常需要存储子对象,定义有子部件的部件行为, /// 并实现在Component里面定义的与子部件有关的操作 /// public class Composite : Component { /// /// 用来存储组合对象中包含的子组件对象 /// private List
childComponents = null; ///
/// 示意方法,通常在里面需要实现递归的调用 /// 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
(); } 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
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
(); } childComponents.Add(component); } public override void RemoveChild(Component component) { if (childComponents == null) { childComponents = new List
(); } 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来操作,因此,新定义的CompositeLeaf子类能够很容易的与已有的结构一起工作,而客户端不需要为增添了新的组件类而改变

很难限制组合中的组件类型

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

6.何时选用组合模式

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

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

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

7.组合模式的本质

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

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

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

转载地址:http://dkkjl.baihongyu.com/

你可能感兴趣的文章
我的友情链接
查看>>
分组计算描述性统计量
查看>>
redhat安装ibm,rdac多路径的奇葩经历
查看>>
【Python之旅】第三篇(二):Pickle序列化
查看>>
移动端keyup事件 ios端 输入框实时变化
查看>>
CSS中选择器权重计算的例题
查看>>
yum提示another app is currently holding the yum lock
查看>>
5款靠谱的安卓备份应用
查看>>
ContentProvider+ContentResolver+ContentObserver
查看>>
ORA-28001: the password has expired解决方法
查看>>
获取不同尺寸3.5/4.0的屏幕大小和系统ios 6/7的版本
查看>>
DOM和IE获取滚动条滚动的距离
查看>>
脚本类模型(客户端网页编程)
查看>>
LVS负载均衡配置与实现
查看>>
java中常见的IO体系
查看>>
storm java 编程思路
查看>>
大数据技术创新呈现“原创-开源-产品化”的阶梯格局
查看>>
spark combineByKey
查看>>
NMath矩阵分解的两种方式
查看>>
启动AVD时log提示“emulator-X disconnected! Cancelling 'X activity launch'!”
查看>>