• 设计模式之组合模式(十四)


    一、引出模式

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

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

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

    代码示例:

    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);
                }
            }
        }

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

    区分了组合对象和叶子对象,并进行有区别的对待,比如在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);
            }
    
            /// <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来操作,因此,新定义的CompositeLeaf子类能够很容易的与已有的结构一起工作,而客户端不需要为增添了新的组件类而改变

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

    6.何时选用组合模式

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

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

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

    7.组合模式的本质

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

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

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

  • 相关阅读:
    Vue单页面应用
    MVVM模式理解
    Ajax原生四大步骤
    Vue 全家桶介绍
    原生js的dom操作
    vs2015+opencv3.3.1+ maxflow-v3.01 c++实现Yuri Boykov 的Interactive Graph Cuts
    c++迭代递归实现汉诺塔(5种迭代方法满足你)
    opencv3.3.1+vs2015+c++实现直接在图像上画掩码,保存掩码图片
    声明函数指针、回调函数、函数对象------c++程序设计基础、编程抽象与算法策略
    C++/C语言的标准库函数与运算符的区别new/delete malloc/free
  • 原文地址:https://www.cnblogs.com/zxj159/p/3484432.html
Copyright © 2020-2023  润新知