系统架构技能之设计模式组合模式

一、上篇回顾

      我们上篇主要讲述了结构型模式中的外观模式,外观模式作为结构型模式中的一个简单又实用的模式,外观模式通过封装细节来提供大粒度的调用,

直接的好处就是,封装细节,提供了应用写程序的可维护性和易用性。外观模式一般应用在系统架构的服务层中,当我们是多个不同类型的客户端应用程序

时,比如一个系统既可以在通过Web的形式访问,也可以通过客户端应用程序的形式时,可能通过外观模式来提供远程服务,让应用程序进行远程调用,

这样通过外观形式提供服务,那么不管是什么样的客户端都访问一致的外观服务,那么以后就算是我们的应用服务发生变化,那么我们不需要修改没一个客

户端应用的调用,只需要修改相应的外观应用即可。

我们主要是讲述了以下的几种情况,使用外观模式可能更适合:

          1、我们在使用第三方类库或者API的时候,我们通过本地的API接口的封装,来完成对第三方API接口的粗粒度外观对象,通过这个外观对象可以

很容易的完成服务的调用。

          2、我们在架构设计的过程中,一次的功能访问可能需要同时的调用很多个对象,那么如果我们在服务调用的时候,能够在应用程序调用中一次就

能完成所有要同时调用的对象那该多好啊,外观模式无疑是最好的原则,特别是在分布式应用中,通过远程调用服务,通过外观模式降低应用程序与服务的

交互次数,同时可以降低应用程序的复杂性。

二、摘要

      本文将会讲述结构性模式中的另外一个常用的模式-组合模式,我们平时在面向对象的设计中,我想有一个原则经常被提及就是,我们在设计的时候,

对象组合>类的继承,本篇将会将结合简单的实例来说明这方面的优势,并且完成对组合模式的主题思想的掌握。我们这样来简单的理解组合模式,组合模

式就是把一些现有的对象或者元素,经过组合后组成新的对象,新的对象提供内部方法,可以让我们很方便的完成这些元素或者内部对象的访问和操作。我

们也可以把组合对象理解成一个容器,容器提供各种访问其内部对象或者元素的API,我们只需要使用这些方法就可以操作它了。那么对象组合相比继承的

优势有哪些呢?可能具体的优势,也不是一句二句就能表述清楚的,还是我们来看看图形的可视化的描述吧。

clip_image002我们这里设计的是持久化服务写到一个基类中,

然后继承自该基类的子类都会拥有内置的持久化方法,可能后续我们又要添加其他的针对某个具体的对象类,有一些个性化的服务,我们通过扩展这个类,

进行继承,这样多重继承后,会有一个很大的问题。子类膨胀的问题,而且一般来说继承的重数达到5层左右的时候,性能上可能就会有一定的瓶颈,。也

不是好的设计的思路。这时候对象组合可能为我们提供了更好的解决方案。基于组合方式的话,可能我们可以这样来做,换个思路:就像我们的一个负责的

对象,可以通过简单的对象来组成的道理差不多,其实。

clip_image004通过上图,我们知道了,组合对象可以

看作是一系列简单的对象组合成负责的对象的一个模式,复杂对象可以看作是简单对象的一个容器。这个复杂对象完成了简单对象的封装,通过这个容器完

成对象内部简单对象的访问。

三、本文大纲

       a、上篇回顾。

       b、摘要。

       c、本文大纲。

       d、组合模式的特点及使用场景。

       e、组合模式的经典实现。

       f、组合模式的其他方案。

       g、原型模式使用总结。

       h、系列进度。

       i、下篇预告。

四、组合模式的特点及使用场景

     组合模式是将一系列对象组合成树形结构用来表示整体和部分之间的关系,组合模式的主要目的是达到,访问组合对象和访问单个对象具有一致性。这里的组合对象比较特殊,本身他可以是由其他的对象组合而成,同时,这个组合对象又可以是组成更复杂对象的一个部分。我们来举个例子来说明吧,可能更直观。

clip_image006这里我们可以理解为一个简单的查询组件可能有要满足

上述的几类查询条件的输入类型,将这个组件作为一个容器,那么同事,这个容器又可以作为另外一个容器的组件来运行,那么我们又可以在分页控件中,

将这个查询组件包含到其中。以为分页提供相应的查询元素的集成。

我们一般在如下场景中使用组合模式比较方便:

         1、我们有的时候想用户使用一个复杂对象像使用简单对象一样的方式去访问,并且用户同意使用对象内部的所有的对象时,我们可以考虑使用该模

式。这个怎么理解呢?我们使用复杂对象像使用简单对象一样的方式去访问的话,那么我们可以使用组合对象,我们把这些简单的对象进行组合,用组合对

象进行包装,并且提供相应的操作组合对象内部的方法。结合上图中的例子,我们可以这样理解,我们把查询组件封装成一个用户控件,然后可以在其他的

页面中进行调用。这个时候,我们可能考虑如何分部页面,如何能够动态的维护界面上的控件,是否显示,控件里面显示的文本和值是什么?等等,这些都

是我们可以考虑的元素,那么我们如何来做呢?提供下面的几个方法。我还是给出图来说话吧:

clip_image008包含上面的这些元素,那么我们可以通过一些相应的方法来进行访

问内部的元素。我们给出简单的示例代码:

        public QueryControl() 
        { 
            InitializeComponent();

            this.InitControlList(); 
        }

        public event EventHandler handler;

        private Dictionary<string,Control> _controlList = null; 
        /// <summary> 
        /// 初始化控件信息 
        /// </summary> 
        private void InitControlList() 
        { 
            this._controlList = new Dictionary<string,Control>(); 
        } 
        /// <summary> 
        /// 返回界面控件中所有的查询条件控件列表 
        /// </summary> 
        /// <returns></returns> 
        public Dictionary<string,Control> GetControls() 
        { 
            return this._controlList; 
        }

        /// <summary> 
        /// 添加查询控件到界面中 
        /// </summary> 
        /// <param name="control"></param> 
        /// <returns></returns> 
        public bool AddControl(string key,Control control) 
        { 
            if(this._controlList.ContainsKey(key)) 
                return false; 
             this._controlList.Add(key,control);

             return true; 
        }

        /// <summary> 
        /// 移除指定键值的对象 
        /// </summary> 
        /// <param name="key"></param> 
        /// <returns></returns> 
        public bool RemoveControl(string key) 
        { 
            if (this._controlList.ContainsKey(key)) 
                return false; 
            this._controlList.Remove(key);

            return true; 
        }

        public virtual void OnQuery() 
        { 
            if (this.handler != null) 
                handler(this, new EventArgs()); 
        }

        public void Query(object sender, EventArgs e) 
        { 
            this.CreateSQL(); 
            this.OnQuery(); 
        }

        /// <summary> 
        /// 根据选中的条件生成SQL语句 
        /// </summary> 
        private void CreateSQL() 
        { 
            throw new NotImplementedException(); 
        }

上面的代码很简单就是给出了基本的思路,并不是完整的功能。我们来看其他的可能会用到组合模式的情况

2、如果有的时候,我们希望用户不了解自己使用的对象有多复杂,并且组合对象可以的内部可以自由的变化和组合,但是不会影响到客户应用程序使用这

个组合对象,如果项目中需要新增一个组合对象的时候,客户调用的程序还是一样,不会因为这个组合对象发生变更而发生变化。组合模式在解决整体和部

分之间的问题应用很广泛,也可以降低系统的复杂度,因为我们可以把复杂的组件看作另一个组件的组成部分来处理。就像上面的查询组件,那么我们可以

把查询组件作为分页组件的一个部分来处理。我这里就不给出示例代码了通过上面的情况,我们可以大概的知道,组合模式的使用场景。下面我们就要结合

实例说明组合模式的用处了。

五、组合模式的经典实现

      我们先来看看使用经典的组合模式来实现我们上面说的查询控件吧,看看和其他的方案有什么样的不同,不过大体的思路都是一致的,把其他的对象

进行组合成复杂的对象,然后提供操作内部对象的方法,具体的方式可以很多。我们这里还是以上面的查询组件和分页组件为例来说明具体的实现思路,当

然我这里只是给出核心代码,但不是完整的代码:

      我们先给出控件内部的完整定义:

        public QueryPanel() 
        { 
            InitializeComponent();

            this.InitControlList(); 
        }

        public event EventHandler handler;

        private Dictionary<string,Control> _controlList = null; 
        /// <summary> 
        /// 初始化控件信息 
        /// </summary> 
        private void InitControlList() 
        { 
            this._controlList = new Dictionary<string,Control>(); 
        } 
        /// <summary> 
        /// 返回界面控件中所有的查询条件控件列表 
        /// </summary> 
        /// <returns></returns> 
        public Dictionary<string,Control> GetControls() 
        { 
            return this._controlList; 
        }

        /// <summary> 
        /// 添加查询控件到界面中 
        /// </summary> 
        /// <param name="control"></param> 
        /// <returns></returns> 
        public bool AddControl(string key,Control control) 
        { 
            if(this._controlList.ContainsKey(key)) 
                return false; 
             this._controlList.Add(key,control);

             return true; 
        }

        /// <summary> 
        /// 移除指定键值的对象 
        /// </summary> 
        /// <param name="key"></param> 
        /// <returns></returns> 
        public bool RemoveControl(string key) 
        { 
            if (this._controlList.ContainsKey(key)) 
                return false; 
            this._controlList.Remove(key);

            return true; 
        }

        public virtual void OnQuery() 
        { 
            if (this.handler != null) 
                handler(this, new EventArgs()); 
        }

        public void Query(object sender, EventArgs e) 
        { 
            this.CreateSQL(); 
            this.OnQuery(); 
        }

        /// <summary> 
        /// 根据选中的条件生成SQL语句 
        /// </summary> 
        private void CreateSQL() 
        { 
            throw new NotImplementedException(); 
        }

        public virtual Control this[string key] 
        { 
            get 
            { 
                return this._controlList[key]; 
            } 
            set 
            { 
                this._controlList[key] = value; 
            } 
        }

        public virtual IEnumerable<Control> GetControlList() 
        { 
            if (this._controlList.Count > 0) 
            { 
                foreach (Control control in this._controlList.Values) 
                { 
                    foreach (Control item in control.Controls) 
                    { 
                        yield return control; 
                    } 
                } 
            } 
        }

     这时候我们如果想要重写上面的方案,可能有时候我们的查询控件需要基于上面的几个基本的操作方法进行重写,完成自定义的组合对象的操作:

    public partial class QueryPanelText : QueryPanel 
    { 
        public QueryPanelText() 
        { 
            InitializeComponent(); 
        }

        public override Control this[string key] 
        { 
            get 
            { 
                return base.GetControls()[key]; 
            } 
            set 
            { 
                base.GetControls()[key] = value; 
            } 
        }

        /// <summary> 
        /// 获取集合中的所有控件集合 
        /// </summary> 
        /// <returns></returns> 
        public override IEnumerable<Control> GetControlList() 
        { 
            return base.GetControlList(); 
        } 
    }

      当然我上面并没有改变对象的任何行为,这里定义了公共的行为,有的时候我们需要限制一些对象内部对象的某些行为,这时候我们通过继承公共的

对象来重写对象的行为来完成。例如从组合对象衍生出来的某些组合对象,可能不具有父类的某些行为时,我们可以通过重写父类的行为来完成自定义的操

作。

六、组合模式的其他方案

        6.1、通过过滤器来改进上述方案。

     有的时候,我们可能并不关心对象中的所有的元素,我们只是操作其中的具有同类特征的对象,比如对于上面的查询控件中的对象,我们可能想要获

取设置条件的控件项,或者说我们相应控制文本框或者下拉框的样式或者高度等等,这个时候,我们需要对组合对象内的元素就行筛选。这个时候,我们可

能可以采用过滤器来实现这样的功能。

      对于上面的结果,我们可能需要获取界面中所有的文本框,或者下拉框,或者其他类型的控件等,这时候我们可以如下方式来做,通过接口定义,根

据传入的不同的过滤器,返回过滤后的结果集合。我们先定义一个过滤器接口:

    public interface ISelectRule 
    { 
        bool IsMatch(Control control); 
    }

    我们这里定义一个文本过滤器的实现。

    public class TextSelect : ISelectRule 
    { 
        #region ISelectRule 成员

        public bool IsMatch(System.Windows.Forms.Control control) 
        { 
            if (control is System.Windows.Forms.TextBox) 
                return true;

            return false; 
        }

        #endregion 
    }

    我们来看看具体的查询控件内部的获取内部元素的方法:

        /// <summary> 
        /// 获取集合中的所有控件集合 
        /// </summary> 
        /// <returns></returns> 
        public  IEnumerable<Control> GetControlList(ISelectRule rule) 
        { 
            foreach (Control control in base.GetControls().Values) 
            { 
                if (rule.IsMatch(control)) 
                    yield return control; 
            } 
        }

     通过上面的几行代码的改进,我们就可以完成对内部的组合对象的改进,这样就可以完成根据不同的查询规则,来返回不同的内部对象集合。

通过上面的迭代器来封装我们的内部组合对象,我们通过迭代器来完成组合对象的过滤。我们还可以通过XML文件来组织我们的对象组合的结构,XML文

件先天性的优势就是结构化的语言,特别适合这样的整体与局部关系的结构,所以我们的组合对象也可以通过XML文件来组织,可以将复杂对象设置为一

个复杂的节点,该节点下包含多个对象时,我们只需要将每个对象设置为一个节点,通过XPath语言完成查询。

6.2XML文件格式的组合模式

使用XML文件来完成组合模式有如下优点:

1、我们有系统提供的类库自动完成对XML文件的操作和访问。

2、XML提供了基于XPath解析的查询语言。

也有一些点,就是操作XML文件,调试起来会比较麻烦,而且进行代码编写和测试是个不太方便的事情。

我们来看看如果我们把上面的获取迭代器的代码,还原成XML文件的格式该如何书写呢?

<?xml version="1.0" encoding="utf-8" ?> 
<Composite> 
  <QueryPanel> 
    <DropDownList name="" type=""/> 
    <DropDownList name="" type=""/> 
    <DropDownList name="" type=""/> 
    <TextBox name="" value="" /> 
    <TextBox name="" value="" /> 
    <TextBox name="" value="" /> 
    <ComboBox name="" value="" /> 
    <ComboBox name="" value="" /> 
    <ComboBox name="" value="" /> 
    <Button name="" value="" /> 
    <Button name="" value="" /> 
    <Button name="" value="" /> 
  </QueryPanel> 
</Composite>

对于上面的XML文件,那么我们如何获取到这个XML文件中的某些类型的节点呢?比如我如何获取到TextBox呢?这时候我们可以使用Xpath语法来进行

选择:

   public class XMLSelect 
   { 
       string xPath = "/Composite/QueryPanel/TextBox"; 
       private XmlNodeList list = null; 
       public XmlNodeList GetNodes() 
       { 
           System.Xml.XmlDocument doc = new XmlDocument(); 
           doc.LoadXml("Composite.xml");

           list = doc.SelectNodes(xPath);

           return list; 
       }

       public List<Control> GetControls() 
       { 
           List<Control> listControls = new List<Control>();

           foreach (XmlNode node in list) 
           { 
               System.Windows.Forms.TextBox textBox = new TextBox(); 
               textBox.Name = node.Attributes["name"].Value.ToString();

               listControls.Add(textBox); 
           }

           return listControls; 
       } 
   }

基于上面的形式我们也可以完成组合模式的要求,通过上面的讲解,我们应该对组合模式有了一定的了解。

七、组合模式使用总结

       通过上面的简单讲解,我们知道了,组合模式意图是通过整体与局部之间的关系,通过树形结构的形式进行组织复杂对象,屏蔽对象内部的细节,对

外展现统一的方式来操作对象,是我们处理更复杂对象的一个手段和方式。本文以查询控件为例,说明了,查询控件内部的组成元素,及如何操作内部的组

成元素,包括添加元素,删除和处理相应事件的Handler,当然组合模式的作用远比这些强大,后面我们肯定会在一些实例代码中运用到组合模式的。组合

模式如果在条件允许的情况下,我们尽量使用组合模式来处理复杂对象,远比通过继承出来的对象来的有效。

       由于本人水平有限,加之理解有误,错误之处还请大家批评指出,多谢大伙的支持和关注!

原文地址:https://www.cnblogs.com/ywsoftware/p/2887045.html