Composite
组合模式:也成为整体-部分模式 ,通过将单个对象(叶子节点)和组合对象(树枝节点)用相同的接口进行表示,使得客户对单个对象和组合对象的使用具有一致性。
image.png
所谓的根节点、树枝节点、叶子节点如上图所示。在实际项目中,如果严格区分三个节点类型,那么对于客户端调用而言,就必须辨认各个节点类型。 组合模式的核心就是:让所有的节点都实现同一个接口,让所有节点操作都具备一致性,这样就能给客户端调用带来极大的便利。
在代码实现上,组合模式分为两种:

  • 透明模式
  • 安全模式

透明模式:
树节点和根节点全部具备一致行为,如下图所示
image.png
我们看见抽象接口(IComponent)中直接包含了Add、Remove、GetChild方法,其实这些方法对于叶子节点来说是没有的。那么我们在具体实现的时候,这些方法在必须实现的情况下只能使用throw new NotImplementedException()来标记或者直接为空实现,这样会带来一些安全隐患,而且不符合接口隔离原则。但是这样的一个好处是客户端无需分辨是叶子还是树枝节点。
安全模式:
将树枝节点和叶子节点的不同行为逻辑分开,如下图
image.png
安全模式下,在抽象节点(IComponent)中只需要定义基础行为,树枝节点本身的操作,如Add、Remove、GetChild直接放在树枝节点中。 叶子节点无需实现树枝节点的逻辑操作。这样可以把树枝节点和叶子节点都不同行为完全分开,符合单一职责原则接口隔离原则。但是对于客户端来说,就要稍微区分下是树枝节点还是叶子节点了。

1、场景描述

组合模式使用的场景一般都有一种包含的关系,我们使用组合模式来实现文件、文件夹的包含关系,使用安全模式。

2、实现

  1. /// <summary>
  2. /// 抽象节点(IComponent)
  3. /// </summary>
  4. public interface IDirectory
  5. {
  6. /// <summary>
  7. /// 显示
  8. /// </summary>
  9. void Show();
  10. }
  11. /// <summary>
  12. /// 文件(叶子节点)
  13. /// </summary>
  14. public class File: IDirectory
  15. {
  16. private string fileName;
  17. public File(string fileName)
  18. {
  19. this.fileName = fileName;
  20. }
  21. public void Show()
  22. {
  23. Console.WriteLine($"这是一个文件,文件名:${fileName}");
  24. }
  25. }
  26. /// <summary>
  27. /// 文件夹
  28. /// </summary>
  29. public class Folder : IDirectory
  30. {
  31. private string _folderName;
  32. private List<IDirectory> _childrens = new List<IDirectory>();
  33. public Folder(string folderName)
  34. {
  35. _folderName = folderName;
  36. }
  37. /// <summary>
  38. /// 添加
  39. /// </summary>
  40. /// <param name="directory"></param>
  41. public void Add(IDirectory directory)
  42. {
  43. _childrens.Add(directory);
  44. }
  45. /// <summary>
  46. /// 移除
  47. /// </summary>
  48. /// <param name="directory"></param>
  49. public void Remove(IDirectory directory)
  50. {
  51. if (_childrens.Contains(directory))
  52. _childrens.Remove(directory);
  53. }
  54. /// <summary>
  55. /// 获取子节点
  56. /// </summary>
  57. /// <returns></returns>
  58. public List<IDirectory> GetChildren()
  59. {
  60. return _childrens;
  61. }
  62. public void Show()
  63. {
  64. Console.WriteLine($"这是一个文件夹:{_folderName}");
  65. foreach (var item in _childrens)
  66. {
  67. item.Show();
  68. }
  69. }
  70. }
  71. // 客户端调用
  72. Folder folder1 = new Folder("文件夹1");
  73. Folder folder2 = new Folder("文件夹2");
  74. Folder folder3 = new Folder("文件夹3");
  75. File file1 = new File("文件1");
  76. File file2 = new File("文件2");
  77. File file3 = new File("文件3");
  78. File file4 = new File("文件4");
  79. folder1.Add(file1);
  80. folder1.Add(folder2);
  81. folder2.Add(folder3);
  82. folder2.Add(file2);
  83. folder3.Add(file3);
  84. folder3.Add(file4);
  85. folder1.Show();
  86. Console.WriteLine("***********************");
  87. folder3.Remove(file3);
  88. folder1.Show();

3、优缺点

优点:

  • 屏蔽各层次之间的差异,使得客户端可以一致的处理不同层次,不用关心差异。
  • 组合(树枝节点)加入新的节点,不会更改源代码,符合“开闭原则”

缺点:

  • 设计比较复杂、层次关系比较复杂

    4、应用场景

    只要是整体和部分关系、成树形结构的对象关系都可以使用组合模式来实现。比如树形菜单,目录结构、组织架构等等。