找回密码
 立即注册
首页 业界区 业界 c#组合模式详解

c#组合模式详解

账暴 2025-6-9 08:20:36
一、基础介绍:

组合模式用于表示部分-整体层次结构。适用于希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象的情况。
顾名思义,什么叫部分-整体,比如常见的前端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. 2     /// 抽象组件类(Component)
    3. 3     /// </summary>
    4. 4     public abstract class DocumentComponent
    5. 5     {
    6. 6         public string Name { get; set; }
    7. 7         protected List<DocumentComponent> mChildren;
    8. 8         public List<DocumentComponent> Children
    9. 9         {
    10. 10             get { return mChildren; }
    11. 11         }
    12. 12         public DocumentComponent(string name)
    13. 13         {
    14. 14             this.Name = name;
    15. 15             mChildren = new List<DocumentComponent>();
    16. 16         }
    17. 17
    18. 18         
    19. 19         public abstract void AddChild(DocumentComponent document);
    20. 20
    21. 21         public abstract void RemoveChild(DocumentComponent document);
    22. 22
    23. 23         public abstract void Show();
    24. 24     }
    复制代码
    接口或抽象类,为节点组件和容器组件对象声明接口,在该类中包含共有行为的声明。
    在抽象组件类中,定义了访问及管理它的子组件的方法。
    本实例中Show为节点和容器组件共有方法,AddChild和RemoveChild为容器组件方法。
    本类主要是为了让节点类和容器类进行继承方便统一管理
     
    2).节点组件类
    1. 1     /// <summary>
    2. 2     /// 节点组件类(Leaf),各类文档,每类型可以添加一个对应类。
    3. 3     /// </summary>
    4. 4     public sealed class Word : DocumentComponent
    5. 5     {
    6. 6         public Word(string name)
    7. 7             : base(name)
    8. 8         { }
    9. 9         public override void AddChild(DocumentComponent document)
    10. 10         {
    11. 11             throw new Exception("节点类不支持");
    12. 12         }
    13. 13
    14. 14         public override void RemoveChild(DocumentComponent document)
    15. 15         {
    16. 16             throw new Exception("节点类不支持");
    17. 17         }
    18. 18
    19. 19         public override void Show()
    20. 20         {
    21. 21             Console.WriteLine("这是一篇word文档:" + Name);
    22. 22         }
    23. 23     }
    复制代码
    节点对象为最小组件(可以理解为树叶),并继承自抽象组件类,实现show方法。
    AddChild和RemoveChild为容器组件方法,在节点类中抛出异常即可。
    该类是最小单位,没有子节点。
    本类一个word文档对象,如果有多个类型的文档,可以声明多个类。
     
    3).容器组件类
    1. 1     /// <summary>
    2. 2     /// 容器组件类(Composite),文件夹
    3. 3     /// </summary>
    4. 4     public class Folder : DocumentComponent
    5. 5     {
    6. 6         public Folder(string name)
    7. 7             : base(name)
    8. 8         { }
    9. 9         public override void AddChild(DocumentComponent document)
    10. 10         {
    11. 11             mChildren.Add(document);
    12. 12             Console.WriteLine("文档或文件夹增加成功");
    13. 13         }
    14. 14         public override void RemoveChild(DocumentComponent document)
    15. 15         {
    16. 16             mChildren.Remove(document);
    17. 17             Console.WriteLine("文档或文件夹删除成功");
    18. 18         }
    19. 19         public override void Show()
    20. 20         {
    21. 21             Console.WriteLine("这是一个文件夹:" + Name);
    22. 22         }
    23. 23     }
    复制代码
    容器对象可以包含无数节点对象和无数容器组件(可以理解为树枝,可以有无数树叶或者分支),容器对象需要实现管理子对象的方法,如AddChild、RemoveChild等。
    本类是一个文件夹对象。
     
    4).客户端
    1. 1     /// <summary>
    2. 2     /// 客户端
    3. 3     /// </summary>
    4. 4     class Client
    5. 5     {
    6. 6         /// <summary>
    7. 7         /// 广度优先检索
    8. 8         /// </summary>
    9. 9         /// <param name="component"></param>
    10. 10         private static void BreadthFirstSearch(DocumentComponent component)
    11. 11         {
    12. 12             Queue<DocumentComponent> q = new Queue<DocumentComponent>();
    13. 13             q.Enqueue(component);
    14. 14             Console.WriteLine(component.Name);
    15. 15             while (q.Count > 0)
    16. 16             {
    17. 17                 DocumentComponent temp = q.Dequeue();
    18. 18                 List<DocumentComponent> children = temp.Children;
    19. 19                 foreach (DocumentComponent child in children)
    20. 20                 {
    21. 21                     Console.WriteLine(child.Name);
    22. 22                     q.Enqueue(child);
    23. 23                 }
    24. 24             }
    25. 25         }
    26. 26
    27. 27         /// <summary>
    28. 28         /// 深度优先检索
    29. 29         /// </summary>
    30. 30         /// <param name="component"></param>
    31. 31         private static void DepthFirstSearch(DocumentComponent component)
    32. 32         {
    33. 33             Console.WriteLine(component.Name);
    34. 34             List<DocumentComponent> children = component.Children;
    35. 35             if (children == null || children.Count == 0) return;
    36. 36             foreach (DocumentComponent child in children)
    37. 37             {
    38. 38                 DepthFirstSearch(child);
    39. 39             }
    40. 40         }
    41. 41
    42. 42         static void Main(string[] args)
    43. 43         {
    44. 44             Console.WriteLine("创建三个目录:");
    45. 45             Folder folder = new Folder("根目录");
    46. 46             Folder folder1 = new Folder("子目录1");
    47. 47             Folder folder2 = new Folder("子目录2");
    48. 48
    49. 49             Console.WriteLine("\r\n创建两个文档:");
    50. 50             Word word1 = new Word("word文档1");
    51. 51             Word word2 = new Word("word文档2");
    52. 52
    53. 53             Console.WriteLine("\r\n将子目录1添加到根目录下:");
    54. 54             folder.AddChild(folder1);
    55. 55             Console.WriteLine("\r\n将子目录2添加到子目录1下:");
    56. 56             folder1.AddChild(folder2);
    57. 57
    58. 58             Console.WriteLine("\r\n将word文档1添加到子目录2下:");
    59. 59             folder2.AddChild(word1);
    60. 60             Console.WriteLine("\r\n将word文档2添加到根目录下:");
    61. 61             folder.AddChild(word2);
    62. 62
    63. 63             Console.WriteLine("\r\n广度优先列表:");
    64. 64             DepthFirstSearch(folder);
    65. 65             Console.WriteLine("\r\n深度优先列表:");
    66. 66             BreadthFirstSearch(folder);
    67. 67
    68. 68             Console.ReadKey();
    69. 69         }
    70. 70
    71. 71
    72. 72     }
    复制代码
    1.png

    注:BreadthFirstSearch为广度优先检索,依次列出所有元素。DepthFirstSearch为深度优先检索,列举完一个文件夹后,返回根目录继续列举其他文件夹。
    通过上述实例可以看出,文件夹可以创建N个子文件夹,但文档只能放在文件夹中,无法放在另一个文档中。
  • 安全方式
    1.   1     /// <summary>
    2.   2     /// 抽象组件类(Component)
    3.   3     /// </summary>
    4.   4     public abstract class DocumentComponent
    5.   5     {
    6.   6         public string Name { get; set; }
    7.   7         protected List<DocumentComponent> mChildren;
    8.   8         public List<DocumentComponent> Children
    9.   9         {
    10. 10             get { return mChildren; }
    11. 11         }
    12. 12         public DocumentComponent(string name)
    13. 13         {
    14. 14             this.Name = name;
    15. 15             mChildren = new List<DocumentComponent>();
    16. 16         }
    17. 17
    18. 18         public abstract void Show();
    19. 19     }
    20. 20
    21. 21     /// <summary>
    22. 22     /// 节点组件类(Leaf),各类文档,每类型可以添加一个对应类。
    23. 23     /// </summary>
    24. 24     public sealed class Word : DocumentComponent
    25. 25     {
    26. 26         public Word(string name)
    27. 27             : base(name)
    28. 28         { }
    29. 29
    30. 30         public override void Show()
    31. 31         {
    32. 32             Console.WriteLine("这是一篇word文档:" + Name);
    33. 33         }
    34. 34     }
    35. 35
    36. 36     /// <summary>
    37. 37     /// 容器组件类(Composite),文件夹
    38. 38     /// </summary>
    39. 39     public class Folder : DocumentComponent
    40. 40     {
    41. 41         public Folder(string name)
    42. 42             : base(name)
    43. 43         { }
    44. 44         public void AddChild(DocumentComponent document)
    45. 45         {
    46. 46             mChildren.Add(document);
    47. 47             Console.WriteLine("文档或文件夹增加成功");
    48. 48         }
    49. 49         public void RemoveChild(DocumentComponent document)
    50. 50         {
    51. 51             mChildren.Remove(document);
    52. 52             Console.WriteLine("文档或文件夹删除成功");
    53. 53         }
    54. 54         public override void Show()
    55. 55         {
    56. 56             Console.WriteLine("这是一个文件夹:" + Name);
    57. 57         }
    58. 58     }
    59. 59
    60. 60
    61. 61     /// <summary>
    62. 62     /// 客户端
    63. 63     /// </summary>
    64. 64     class Client
    65. 65     {
    66. 66         /// <summary>
    67. 67         /// 广度优先检索
    68. 68         /// </summary>
    69. 69         /// <param name="component"></param>
    70. 70         private static void BreadthFirstSearch(DocumentComponent component)
    71. 71         {
    72. 72             Queue<DocumentComponent> q = new Queue<DocumentComponent>();
    73. 73             q.Enqueue(component);
    74. 74             Console.WriteLine(component.Name);
    75. 75             while (q.Count > 0)
    76. 76             {
    77. 77                 DocumentComponent temp = q.Dequeue();
    78. 78                 List<DocumentComponent> children = temp.Children;
    79. 79                 foreach (DocumentComponent child in children)
    80. 80                 {
    81. 81                     Console.WriteLine(child.Name);
    82. 82                     q.Enqueue(child);
    83. 83                 }
    84. 84             }
    85. 85         }
    86. 86
    87. 87         /// <summary>
    88. 88         /// 深度优先检索
    89. 89         /// </summary>
    90. 90         /// <param name="component"></param>
    91. 91         private static void DepthFirstSearch(DocumentComponent component)
    92. 92         {
    93. 93             Console.WriteLine(component.Name);
    94. 94             List<DocumentComponent> children = component.Children;
    95. 95             if (children == null || children.Count == 0) return;
    96. 96             foreach (DocumentComponent child in children)
    97. 97             {
    98. 98                 DepthFirstSearch(child);
    99. 99             }
    100. 100         }
    101. 101
    102. 102         static void Main(string[] args)
    103. 103         {
    104. 104             Console.WriteLine("创建三个目录:");
    105. 105             Folder folder = new Folder("根目录");
    106. 106             Folder folder1 = new Folder("子目录1");
    107. 107             Folder folder2 = new Folder("子目录2");
    108. 108
    109. 109             Console.WriteLine("\r\n创建两个文档:");
    110. 110             Word word1 = new Word("word文档1");
    111. 111             Word word2 = new Word("word文档2");
    112. 112
    113. 113             Console.WriteLine("\r\n将子目录1添加到根目录下:");
    114. 114             folder.AddChild(folder1);
    115. 115             Console.WriteLine("\r\n将子目录2添加到子目录1下:");
    116. 116             folder1.AddChild(folder2);
    117. 117
    118. 118             Console.WriteLine("\r\n将word文档1添加到子目录2下:");
    119. 119             folder2.AddChild(word1);
    120. 120             Console.WriteLine("\r\n将word文档2添加到根目录下:");
    121. 121             folder.AddChild(word2);
    122. 122
    123. 123             Console.WriteLine("\r\n广度优先列表:");
    124. 124             DepthFirstSearch(folder);
    125. 125             Console.WriteLine("\r\n深度优先列表:");
    126. 126             BreadthFirstSearch(folder);
    127. 127
    128. 128             Console.ReadKey();
    129. 129         }
    130. 130
    131. 131     }
    复制代码
    从上述实例中可以看出,安全模式其实就是把共有的方法放在抽象类的。
    文件夹独有的方法放在容器类中,这样做保证了节点类就没有Add和Remove等无用方法。
四、总结:

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

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册