c#组合模式详解

基础介绍:

  组合模式用于表示部分-整体层次结构。适用于希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象的情况。

  顾名思义,什么叫部分-整体,比如常见的前端UI,一个DIV标签中可以存在多个A标签、P标签、DIV标签等等。

  相较于DIV这个容器整体而言,其中所含的A标签、P标签甚至是DIV标签都是单个的部分。

  而显示的时候却是一视同仁,不分部分还是整体。

  这就是典型的组合模式。

  再比如WinForms应用程序中,Label、TextBox等这样简单的控件,可以理解为节点对象,它们中无法再插入其他控件,它们就是最小的。

  而比如GroupBox、DataGrid这样由多个简单控件组成的复合控件或者容器,就可以理解为容器对象,它们中可以再插入其他的节点对象,甚至是再插入其他容器对象。

  但不管是Label这种节点对象还是DataGrid这种容器对象,想要显示的话都需要执行OnPaint方法。

  为了表示这种对象之间整体与部分的层次结构,System.Windows.Forms.Control类就是应用了这种组合模式。

  这样就可以简单的把组合模式分为三个部分:

  • 抽象组件类(Component):它可以是接口或抽象类,为节点组件和容器组件对象声明接口,在该类中包含共有行为的声明。在抽象组件类中,定义了访问及管理它的子组件的方法。
  • 节点组件类(Leaf):节点对象为最小组件(可以理解为树叶),并继承自抽象组件类,实现其共有声明和方法。
  • 容器组件类(Composite):容器对象可以包含无数节点对象和无数容器组件(可以理解为树枝,可以有无数树叶或者分支),容器对象需要实现管理子对象的方法,如Add、Remove等。

应用场景:

  当发现需求中是体现部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑使用组合模式了

  UI的一系列控件就是使用了组合模式,整体和部分可以被一致对待。

  组合模式有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。

  以下情况下适用Composite模式:

  1.对象的部分-整体层次结构。

  2.忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象。

创建方式:

 组合模式实现的最关键的地方是——简单对象和复合对象必须实现相同的接口。这就是组合模式能够将组合对象和简单对象进行一致处理的原因。

  组合模式有两种实现方式,一种是:透明式的组合模式,另外一种是:安全式的组合模式。

 

  透明方式————————————————

  Leaf叶类中也有Add 与 Remove方法,这种方式叫透明方式。

  也就是说在Component中声明所有用来管理子对象的方法,其中包括Add、Remove等。

  这样实现Component接口的所有子类都具备了Add与Remove。

  这样做的好处是叶节点和枝节点对于外界没有区别,它们具有一致的行为接口。

  但问题也很明显,因为Leaf类本身不具备Add、Remove方法的功能,其实现是没有意义的。

 

  安全方式————————————————

  在Component接口中不去声明Add与Remove方法,那么子类Leaf也就不用必须实现它们,而在Composite类中声明所有用来管理子类对象的方法。
  
  

  以文档管理器为例,文件夹为Composite,各类文档为Leaf

  1. 透明方式

     1.抽象类

     1     /// <summary>  2     /// 抽象组件类(Component)  3     /// </summary>  4     public abstract class DocumentComponent  5     {  6         public string Name { get; set; }  7         protected List<DocumentComponent> mChildren;  8         public List<DocumentComponent> Children  9         { 10             get { return mChildren; } 11         } 12         public DocumentComponent(string name) 13         { 14             this.Name = name; 15             mChildren = new List<DocumentComponent>(); 16         } 17  18          19         public abstract void AddChild(DocumentComponent document); 20  21         public abstract void RemoveChild(DocumentComponent document); 22  23         public abstract void Show(); 24     }

    接口或抽象类,为节点组件和容器组件对象声明接口,在该类中包含共有行为的声明。

    在抽象组件类中,定义了访问及管理它的子组件的方法。

    本实例中Show为节点和容器组件共有方法,AddChildRemoveChild为容器组件方法。

    本类主要是为了让节点类和容器类进行继承方便统一管理

    2.节点组件类

     1     /// <summary>  2     /// 节点组件类(Leaf),各类文档,每类型可以添加一个对应类。  3     /// </summary>  4     public sealed class Word : DocumentComponent  5     {  6         public Word(string name)  7             : base(name)  8         { }  9         public override void AddChild(DocumentComponent document) 10         { 11             throw new Exception("节点类不支持"); 12         } 13  14         public override void RemoveChild(DocumentComponent document) 15         { 16             throw new Exception("节点类不支持"); 17         } 18  19         public override void Show() 20         { 21             Console.WriteLine("这是一篇word文档:" + Name); 22         } 23     }

    节点对象为最小组件(可以理解为树叶),并继承自抽象组件类,实现show方法。

    AddChildRemoveChild为容器组件方法,在节点类中抛出异常即可。

    该类是最小单位,没有子节点。

    本类一个word文档对象,如果有多个类型的文档,可以声明多个类。

    3.容器组件类

     1     /// <summary>  2     /// 容器组件类(Composite),文件夹  3     /// </summary>  4     public class Folder : DocumentComponent  5     {  6         public Folder(string name)  7             : base(name)  8         { }  9         public override void AddChild(DocumentComponent document) 10         { 11             mChildren.Add(document); 12             Console.WriteLine("文档或文件夹增加成功"); 13         } 14         public override void RemoveChild(DocumentComponent document) 15         { 16             mChildren.Remove(document); 17             Console.WriteLine("文档或文件夹删除成功"); 18         } 19         public override void Show() 20         { 21             Console.WriteLine("这是一个文件夹:" + Name); 22         } 23     }

    容器对象可以包含无数节点对象和无数容器组件(可以理解为树枝,可以有无数树叶或者分支),容器对象需要实现管理子对象的方法,如AddChildRemoveChild等。

    本类是一个文件夹对象。

    4.客户端

     1     /// <summary>  2     /// 客户端  3     /// </summary>  4     class Client  5     {  6         /// <summary>  7         /// 广度优先检索  8         /// </summary>  9         /// <param name="component"></param> 10         private static void BreadthFirstSearch(DocumentComponent component) 11         { 12             Queue<DocumentComponent> q = new Queue<DocumentComponent>(); 13             q.Enqueue(component); 14             Console.WriteLine(component.Name); 15             while (q.Count > 0) 16             { 17                 DocumentComponent temp = q.Dequeue(); 18                 List<DocumentComponent> children = temp.Children; 19                 foreach (DocumentComponent child in children) 20                 { 21                     Console.WriteLine(child.Name); 22                     q.Enqueue(child); 23                 } 24             } 25         } 26  27         /// <summary> 28         /// 深度优先检索 29         /// </summary> 30         /// <param name="component"></param> 31         private static void DepthFirstSearch(DocumentComponent component) 32         { 33             Console.WriteLine(component.Name); 34             List<DocumentComponent> children = component.Children; 35             if (children == null || children.Count == 0) return; 36             foreach (DocumentComponent child in children) 37             { 38                 DepthFirstSearch(child); 39             } 40         } 41  42         static void Main(string[] args) 43         { 44             Console.WriteLine("创建三个目录:"); 45             Folder folder = new Folder("根目录"); 46             Folder folder1 = new Folder("子目录1"); 47             Folder folder2 = new Folder("子目录2"); 48  49             Console.WriteLine("rn创建两个文档:"); 50             Word word1 = new Word("word文档1"); 51             Word word2 = new Word("word文档2"); 52  53             Console.WriteLine("rn将子目录1添加到根目录下:"); 54             folder.AddChild(folder1); 55             Console.WriteLine("rn将子目录2添加到子目录1下:"); 56             folder1.AddChild(folder2); 57  58             Console.WriteLine("rn将word文档1添加到子目录2下:"); 59             folder2.AddChild(word1); 60             Console.WriteLine("rn将word文档2添加到根目录下:"); 61             folder.AddChild(word2); 62  63             Console.WriteLine("rn广度优先列表:"); 64             DepthFirstSearch(folder); 65             Console.WriteLine("rn深度优先列表:"); 66             BreadthFirstSearch(folder); 67  68             Console.ReadKey(); 69         } 70  71  72     }

    c#组合模式详解

    注:BreadthFirstSearch为广度优先检索,依次列出所有元素。DepthFirstSearch为深度优先检索,列举完一个文件夹后,返回根目录继续列举其他文件夹。

    通过上述实例可以看出,文件夹可以创建N个子文件夹,但文档只能放在文件夹中,无法放在另一个文档中。

  2. 安全方式

      1     /// <summary>   2     /// 抽象组件类(Component)   3     /// </summary>   4     public abstract class DocumentComponent   5     {   6         public string Name { get; set; }   7         protected List<DocumentComponent> mChildren;   8         public List<DocumentComponent> Children   9         {  10             get { return mChildren; }  11         }  12         public DocumentComponent(string name)  13         {  14             this.Name = name;  15             mChildren = new List<DocumentComponent>();  16         }  17   18         public abstract void Show();  19     }  20   21     /// <summary>  22     /// 节点组件类(Leaf),各类文档,每类型可以添加一个对应类。  23     /// </summary>  24     public sealed class Word : DocumentComponent  25     {  26         public Word(string name)  27             : base(name)  28         { }  29   30         public override void Show()  31         {  32             Console.WriteLine("这是一篇word文档:" + Name);  33         }  34     }  35   36     /// <summary>  37     /// 容器组件类(Composite),文件夹  38     /// </summary>  39     public class Folder : DocumentComponent  40     {  41         public Folder(string name)  42             : base(name)  43         { }  44         public void AddChild(DocumentComponent document)  45         {  46             mChildren.Add(document);  47             Console.WriteLine("文档或文件夹增加成功");  48         }  49         public void RemoveChild(DocumentComponent document)  50         {  51             mChildren.Remove(document);  52             Console.WriteLine("文档或文件夹删除成功");  53         }  54         public override void Show()  55         {  56             Console.WriteLine("这是一个文件夹:" + Name);  57         }  58     }  59   60   61     /// <summary>  62     /// 客户端  63     /// </summary>  64     class Client  65     {  66         /// <summary>  67         /// 广度优先检索  68         /// </summary>  69         /// <param name="component"></param>  70         private static void BreadthFirstSearch(DocumentComponent component)  71         {  72             Queue<DocumentComponent> q = new Queue<DocumentComponent>();  73             q.Enqueue(component);  74             Console.WriteLine(component.Name);  75             while (q.Count > 0)  76             {  77                 DocumentComponent temp = q.Dequeue();  78                 List<DocumentComponent> children = temp.Children;  79                 foreach (DocumentComponent child in children)  80                 {  81                     Console.WriteLine(child.Name);  82                     q.Enqueue(child);  83                 }  84             }  85         }  86   87         /// <summary>  88         /// 深度优先检索  89         /// </summary>  90         /// <param name="component"></param>  91         private static void DepthFirstSearch(DocumentComponent component)  92         {  93             Console.WriteLine(component.Name);  94             List<DocumentComponent> children = component.Children;  95             if (children == null || children.Count == 0) return;  96             foreach (DocumentComponent child in children)  97             {  98                 DepthFirstSearch(child);  99             } 100         } 101  102         static void Main(string[] args) 103         { 104             Console.WriteLine("创建三个目录:"); 105             Folder folder = new Folder("根目录"); 106             Folder folder1 = new Folder("子目录1"); 107             Folder folder2 = new Folder("子目录2"); 108  109             Console.WriteLine("rn创建两个文档:"); 110             Word word1 = new Word("word文档1"); 111             Word word2 = new Word("word文档2"); 112  113             Console.WriteLine("rn将子目录1添加到根目录下:"); 114             folder.AddChild(folder1); 115             Console.WriteLine("rn将子目录2添加到子目录1下:"); 116             folder1.AddChild(folder2); 117  118             Console.WriteLine("rn将word文档1添加到子目录2下:"); 119             folder2.AddChild(word1); 120             Console.WriteLine("rn将word文档2添加到根目录下:"); 121             folder.AddChild(word2); 122  123             Console.WriteLine("rn广度优先列表:"); 124             DepthFirstSearch(folder); 125             Console.WriteLine("rn深度优先列表:"); 126             BreadthFirstSearch(folder); 127  128             Console.ReadKey(); 129         } 130  131     }

    从上述实例中可以看出,安全模式其实就是把共有的方法放在抽象类的。

    文件夹独有的方法放在容器类中,这样做保证了节点类就没有Add和Remove等无用方法。

总结:

  组合模式解耦了客户程序与复杂元素内部结构,从而使客户程序可以向处理简单元素一样来处理复杂元素。

  

  

发表评论

评论已关闭。

相关文章