9

c#组合模式详解 - 少年真爱

 6 months ago
source link: https://www.cnblogs.com/mingnianjiehunba/p/17735739.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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为节点和容器组件共有方法,AddChild和RemoveChild为容器组件方法。

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

    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方法。

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

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

    本类一个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     }

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

    本类是一个文件夹对象。

    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("\r\n创建两个文档:");
    50             Word word1 = new Word("word文档1");
    51             Word word2 = new Word("word文档2");
    52 
    53             Console.WriteLine("\r\n将子目录1添加到根目录下:");
    54             folder.AddChild(folder1);
    55             Console.WriteLine("\r\n将子目录2添加到子目录1下:");
    56             folder1.AddChild(folder2);
    57 
    58             Console.WriteLine("\r\n将word文档1添加到子目录2下:");
    59             folder2.AddChild(word1);
    60             Console.WriteLine("\r\n将word文档2添加到根目录下:");
    61             folder.AddChild(word2);
    62 
    63             Console.WriteLine("\r\n广度优先列表:");
    64             DepthFirstSearch(folder);
    65             Console.WriteLine("\r\n深度优先列表:");
    66             BreadthFirstSearch(folder);
    67 
    68             Console.ReadKey();
    69         }
    70 
    71 
    72     }
    736258-20231007154358558-1996048766.png

    注: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("\r\n创建两个文档:");
    110             Word word1 = new Word("word文档1");
    111             Word word2 = new Word("word文档2");
    112 
    113             Console.WriteLine("\r\n将子目录1添加到根目录下:");
    114             folder.AddChild(folder1);
    115             Console.WriteLine("\r\n将子目录2添加到子目录1下:");
    116             folder1.AddChild(folder2);
    117 
    118             Console.WriteLine("\r\n将word文档1添加到子目录2下:");
    119             folder2.AddChild(word1);
    120             Console.WriteLine("\r\n将word文档2添加到根目录下:");
    121             folder.AddChild(word2);
    122 
    123             Console.WriteLine("\r\n广度优先列表:");
    124             DepthFirstSearch(folder);
    125             Console.WriteLine("\r\n深度优先列表:");
    126             BreadthFirstSearch(folder);
    127 
    128             Console.ReadKey();
    129         }
    130 
    131     }

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

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

总结:

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK