zoukankan      html  css  js  c++  java
  • 组合模式(Composite)

    1.1.1 摘要

          在软件系统设计中,我们经常会遇到整体和部分的设计问题,例如为一家架构完善的公司设计系统时,我们在系统设计过程中应该更加注重这种整体和部分的关系(总部和分部的关系),这就是我们今天要介绍的组合模式(Composite)。

          组合模式(Composite)把对象构造成为一棵对象树,现在让我们简短回忆一下树的定义。

          树是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:

          每个结点有零个或多个子结点;

          每一个子结点只有一个父结点;

          没有前驱的结点为根结点;

          除了根结点外,每个子结点可以分为m个不相交的子树(参考维基百科);

          使用频率: clip_image001  medium high

     

    • 定义

            组合模式(Composite),将对象组合成树形结构以表示‘部分-整体’的层次关系(树状结构)。组合模式使得用户对单个对象和组合对象使用具有一致性。

     

    • 意图

            希望用户可以忽略单个对象和组合对象的区别,统一使用组合结构中的所有对象(封装变化的思想)。

     

    • 结构图

    composite0

    图1组合模式(Composite)结构图

    • 参与者

            Component

            它是一个抽象角色,为对象的组合提供统一的接口。

            为其派生类提供默认的行为。

            通过该接口来访问和管理Child Components(节点后继)。

            通过该接口来访问Parent Components(节点前驱)。

     

            Leaf

            代表参加组合的叶子对象(叶子没有后继)。

            定义组成原始对象的行为。

     

            Composite

            定义非叶子对象的行为。

            实现Component中的孩子操作行为(如:Add(),Remove() 等)。

     

     

    1.1.2 正文

          在我们学习组合模式(Composite)之前,让我们先讲解一下透明方式和安全方式。

          透明方式:在Component中声明所有用来管理子对象的方法,如Add()方法,Remove()方法及GetChild()方法,所有实现Component接口的子类都具备这些方法,这使得Component和子类具备一致的行为接口,使得对客户端无需区别树叶和树枝对象。

          大家可以回忆一下代理模式(Proxy)中,Proxy,RealSubject类和Subject接口具备一致的行为接口,从而使得被代理者对于客户端是透明的。

          正由于我们的Composite和Leaf都具备一致的接口行为,但我们知道Leaf不应该具有Add(),Remove()及GetChild()方法,因为我们叶子节点不能再添加和移除节点了。

          安全模式:在透明模式基础上把Component中声明所有用来管理子对象的方法移到Composite中,在Composite实现子对象的管理方法,那么Leaf就没有子对象管理方法,这使得Composite和Leaf的行为接口不一致,所以客户端在调用时要知道树叶和树枝对象存在。

     

    composite1

    图2透明方式的组合模式(Composite)结构图

     

     composite2

    图3安全方式的组合模式(Composite)结构图

     

          通过前面的介绍我们知道透明和安全方式组合模式(Composite)的区别在于是否在抽象接口Component中定义子对象的管理行为。现在让我们完成透明方式的组合模式(Composite)。

          以下示例代码演示透明方式的组合模式代码:

          首先我们定义一个抽象类Componet,在其中声明Add(),Remove()和Display()等子对象操作发法及初始化字段name的公共行为。

    /// <summary>
    /// The Component acts as abstract class.
    /// </summary>
    public abstract class Component
    {
        protected string name;
    
    
        public Component()
        {
        }
    
    
        public Component(string name)
        {
            this.name = name;
        }
    
        #region Methods
    
        public abstract Component Add(Component obj);
        public abstract void Remove(Component obj);
        public abstract void Display(int depth);
    
        #endregion
    
    }

          接着我们定义Composite类继承于Component,增加_children保存Component对象的引用,从而建立起由Component到Composite的聚集关系(Has-a关系),并且实现Component抽象类中的抽象方法。

    /// <summary>
    /// The Composite acts as branch.
    /// </summary>
    public class Composite : Component
    {
        private IList<Component> _childen = new List<Component>();
    
    
        public Composite()
        {
        }
    
        public Composite(string name)
            : base(name)
        {
        }
    
    
        /// <summary>
        /// Adds the branch or leaf obj.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public override Component Add(Component obj)
        {
            this._childen.Add(obj);
            return obj;
        }
    
    
        /// <summary>
        /// Removes the branch or leaf obj.
        /// </summary>
        /// <param name="obj">The obj.</param>
        public override void Remove(Component obj)
        {
            this._childen.Remove(obj);
        }
    
    
        /// <summary>
        /// Displays the tree's depth.
        /// </summary>
        /// <param name="depth">The depth.</param>
        public override void Display(int depth)
        {
            StringBuilder depthBuilder = new StringBuilder(new String('-', depth) + name);
            Console.WriteLine(depthBuilder);
    
            foreach (Component child in _childen)
            {
                child.Display(depth + 1);
            }
        }
    
    }

          最后我们定义Leaf类它也是继承于Component,在其中我们实现Component定义的行为接口,这使得Composite和Leaf类具有统一的接口行为,但我们并没有在Leaf的行为方法中给出具体的实现,因为叶子节点并没有后继,所以没有必要实现Add()和Remove()等行为。

    /// <summary>
    /// The "Leaf" acts as Leaf.
    /// </summary>
    public class Leaf : Component
    {
    
        public Leaf(string name)
            : base(name)
        {
        }
    
    
        public Leaf()
        {
        }
    
    
        /// <summary>
        /// Adds the branch or leaf obj.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public override Component Add(Component obj)
        {
            throw new NotSupportedException();
        }
    
    
        /// <summary>
        /// Removes the branch or leaf obj.
        /// </summary>
        /// <param name="obj">The obj.</param>
        public override void Remove(Component obj)
        {
            throw new NotSupportedException();
        }
    
    
        /// <summary>
        /// Displays the tree's depth.
        /// </summary>
        /// <param name="depth">The depth.</param>
        public override void Display(int depth)
        {
            StringBuilder depthBuilder = new StringBuilder(new String('-', depth) + name);
            Console.WriteLine(depthBuilder);
        }
    }

          我们定义一个工厂方法来创建Composite和Leaf对象,大家可以注意到在创建一个Component对象之后,我们调用子对象管理方法时,并不需要判断当前对象是由Composite类,还是有Leaf类创建得来,这就是透明方式的组合模式(Composite)。

    public class Program
    {
        static void Main(string[] args)
        {
            Client client = new Client();
            Component component = client.Get("root");
            Component componentA = component.Add(new Composite("BranchA"));
            componentA.Add(new Leaf("LeafA"));
            componentA.Add(new Leaf("LeafB"));
            Component componentB = component.Add(new Composite("BranchB"));
    
            component.Display(1);
    
            Console.ReadKey();
        }
    }
    

    composite3

    图4输出结果

     

          前面例子给出了透明方式组合模式(Composite)的实现,我们在Component中声明了Add() 和Remove()操作子对象方法,使得Composite和Leaf类具备一致的行为接口,客户端可以正常调用Composite中的操作子对象方法,但如果客户端尝试调用Leaf中操作子对象方法时会抛出异常,因为叶子节点没有后继节点了,假设我们要实现安全方式组合模式(Composite)客户端在调用操作子对象方法时,需要判断当前对象是叶子节点或枝节点。

     

     

    Component component = client.Get("root");
    
    if (component is Composite)
    {
        //// Implemention code.
    }

     

          接下来让我们通过绘图程序实例,介绍安全方式的组合模式(Composite)的实现,首先我们定义一个抽象类DrawingProgramm,其角色为Component,然后再添加三个子类Shape,Triangle和Circle,它们的角色分别为:Composite,Leaf和Leaf。

          现在把子对象的管理方法都移到Shape类中,所以对客户端来说调用子对象时,它了解Shape、Triangle和Circle直接的区别。

     

          以下示例代码演示安全方式的组合模式代码:

          首先我们定义一个抽象类DrawingProgramming(即Componet),在其中声明Draw()及初始化字段_name的公共行为。

    /// <summary>
    /// The "DrawingProgramming" class acts as component.
    /// </summary>
    public abstract class DrawingProgramming
    {
        protected string _name;
        public abstract void Draw();
    
        public DrawingProgramming(string name)
        {
            this._name = name;
        }
    }

          接着我们定义Shape类继承于DrawingProgramming,增加_children保存DrawingProgramming对象的引用,也是建立起由DrawingProgramming到Shape的聚集关系(Has-a关系),大家要注意到安全方式的组合模式(Composite)对子对象的管理方法都移到Shape类(即Composite)中,使得Leaf和Composite的行为接口不一致。

    /// <summary>
    /// The "Shape" class acts as composite.
    /// </summary>
    public class Shape : DrawingProgramming
    {
    
        IList<DrawingProgramming> _children = new List<DrawingProgramming>();
    
    
        public Shape(string name)
            : base(name)
        {
        }
    
        #region DrawingProgramming 成员
    
        public override void Draw()
        {
            StringBuilder depthBuilder = new StringBuilder(_name);
            Console.WriteLine(depthBuilder);
            foreach (DrawingProgramming child in _children)
            {
                child.Draw();
            }
        }
    
        #endregion
    
        #region Methods
    
        public void Add(DrawingProgramming dp)
        {
            _children.Add(dp);
        }
    
        public void Remove(DrawingProgramming dp)
        {
            _children.Remove(dp);
        }
    
        #endregion
    }

          最后我们定义Triangle和Circle类它也是继承于DrawingProgramming,在其中我们实现DrawingProgramming定义的Draw()行为,这使得Composite和Leaf类具有不一致的接口行为,这样我们可以避免在Leaf中实现没有必要地接口,从而使得我们设计符合SRP原则(对象职责单一原则)。

    /// <summary>
    /// The "Triangle" class acts as leaf.
    /// </summary>
    public class Triangle : DrawingProgramming
    {
    
        public Triangle(string name)
            : base(name)
        {
        }
    
        #region DrawingProgramming 成员
    
        public override void Draw()
        {
            StringBuilder depthBuilder = new StringBuilder(_name);
            Console.WriteLine(depthBuilder);
        }
    
        #endregion
    }
    
    
    /// <summary>
    /// The "Circle" class acts as leaf.
    /// </summary>
    public class Circle : DrawingProgramming
    {
        public Circle(string name)
            : base(name)
        {
    
        }
    
        #region DrawingProgramming 成员
    
        public override void Draw()
        {
            StringBuilder depthBuilder = new StringBuilder(_name);
            Console.WriteLine(depthBuilder);
        }
    
        #endregion
    }

        大家注意到在我们给出的例子中,我们都是把对象一个接一个的链接起来,值就是像是一条对象链,不禁让人想起装饰者模式(Decorator)中的装饰对象链。

        组合模式(Composite)和装饰者模式(Decorator)的关系

     decorator

    图5 Decorator中的Composite

     

          通过上图我们可以发现,装饰者模式(Decorator)是通过组合模式(Composite)来构建起一棵对象树,然后通过装饰者的子类来扩展装饰者(即Composite)的行为,从而实现对具体组件装饰行为(即Leaf)。

     

    1.1.3 总结

          1.组合模式(Composite)采用树形层次结构来实现对象容器,如:绘图程序例子我们把各种各样的图形添加的Shape中,从而构造起一条对象链,把“一对多”的关系转化“一对一”的层次关系,使得客户代码可以一致地处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。

         2.组合模式(Composite)中,透明方式和安全方式的使用抉择,虽然透明方式有可能违背面向对象的SRP原则(单一职责),而安全方式没有很好的封装变化,但在实际开发时我们要根据具体的情况权衡利弊。

  • 相关阅读:
    XStream
    Tomcat权威指南-读书摘要系列2
    《人性的弱点》
    HttpClient
    Spring整合Mybatis
    Tomcat权威指南-读书摘要系列1
    MT【88】抽象函数
    MT【87】迭代画图
    MT【86】两个绝对值之和最大
    MT【85】正整数系数
  • 原文地址:https://www.cnblogs.com/rush/p/2102288.html
Copyright © 2011-2022 走看看