定义

将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

结构和说明

结构型模式-组合模式 - 图1

示例代码

  1. public class ComponentDemo {
  2. /**
  3. * 抽象的组件对象,为组合中的对象声明接口,实现接口的缺省行为
  4. */
  5. public static abstract class Component {
  6. /**
  7. * 示意方法,子组件对象可能有的功能方法
  8. */
  9. public abstract void someOperation();
  10. /**
  11. * 向组合对象中加入组合对象
  12. *
  13. * @param child 被加入组合对象中的组件对象
  14. * @throws UnsupportedOperationException
  15. */
  16. public void addChild(Component child) throws UnsupportedOperationException {
  17. // 缺省的实现,抛出例外,因为叶子对象没有这个功能
  18. throw new UnsupportedOperationException("对象不支持这个功能");
  19. }
  20. /**
  21. * 从组合对象中移除某个组件对象
  22. *
  23. * @param child 被移除的组件对象
  24. * @throws UnsupportedOperationException
  25. */
  26. public void removeChild(Component child) throws UnsupportedOperationException {
  27. // 缺省的实现,抛出例外,因为叶子对象没有这个功能
  28. throw new UnsupportedOperationException("对象不支持这个功能");
  29. }
  30. /**
  31. * 返回某个索引对应的组件对象
  32. *
  33. * @param index 需要获取组件对象的索引,索引从 0 开始
  34. * @return 索引对应的组件对象
  35. * @throws UnsupportedOperationException
  36. */
  37. public Component getChildren(int index) throws UnsupportedOperationException {
  38. // 缺省的实现,抛出例外,因为叶子对象没有这个功能
  39. throw new UnsupportedOperationException("对象不支持这个功能");
  40. }
  41. }
  42. /**
  43. * 组合对象,通常需要存储子对象,定义有子部件的部件行为
  44. * 并实现在 Component 里面定义的与子部件有关的操作
  45. */
  46. public static class Composite extends Component {
  47. /**
  48. * 用来存储组合对象中包含的子组件对象
  49. */
  50. private List<Component> childComponents = new ArrayList<>();
  51. /**
  52. * 示意方法,通常在里面需要实现递归的调用
  53. */
  54. @Override
  55. public void someOperation() {
  56. // 递归地进行子组件响应方法的调用
  57. childComponents.parallelStream().forEach(Component::someOperation);
  58. }
  59. @Override
  60. public void addChild(Component child) throws UnsupportedOperationException {
  61. childComponents.add(child);
  62. }
  63. @Override
  64. public void removeChild(Component child) throws UnsupportedOperationException {
  65. childComponents.remove(child);
  66. }
  67. @Override
  68. public Component getChildren(int index) throws UnsupportedOperationException {
  69. return childComponents.get(index);
  70. }
  71. }
  72. /**
  73. * 叶子对象,叶子对象不再包含其他子对象
  74. */
  75. public static class Leaf extends Component {
  76. /**
  77. * 示意方法,叶子对象可能有自己的功能方法
  78. */
  79. @Override
  80. public void someOperation() {
  81. // do something
  82. }
  83. }
  84. public static class Client {
  85. public static void main(String[] args) {
  86. // 定义多个 Composite 对象
  87. Component root = new Composite();
  88. Component c1 = new Composite();
  89. Component c2 = new Composite();
  90. // 定义多个叶子对象
  91. Component leaf1 = new Leaf();
  92. Component leaf2 = new Leaf();
  93. Component leaf3 = new Leaf();
  94. // 组合成为树形的对象结构
  95. // 并不是每次操作组件对象都需要组装树形的对象结构,
  96. // 如果对象结构已经存在,直接操作就可以了
  97. root.addChild(c1);
  98. root.addChild(c2);
  99. root.addChild(leaf1);
  100. c1.addChild(leaf2);
  101. c2.addChild(leaf3);
  102. // 操作 Component 对象
  103. root.someOperation();
  104. }
  105. }
  106. }

优缺点

优点

  • 定义了包含基本对象和组合对象的类层次结构

在组合模式中,基本对象可以被组合成复杂的组合对象,而组合对象又可以组合成更复杂的组合对象,可以不断地递归组合下去,从而构成一个统一的组合对象的类层次结构。

  • 统一了组合对象和叶子对象

在组合模式中,可以把叶子对象当作特殊的组合对象看待,为它们定义统一的父类,从而把组合对象和叶子对象的行为统一起来。

  • 简化了客户端的调用

组合模式通过统一组合对象和叶子对象,使得客户端在使用它们的时候,不需要再去区分它们,客户不关心使用的到底是什么类型的对象,这就大大简化了客户端的使用。

  • 更容易扩展

由于客户端是统一地面对 Component 来操作,因此,新定义的 Composite 或 Leaf 子类能够很容易地与已有的结构一起工作,而客户端不需要为了增添了新的组件类而改变。

缺点

  • 很难限制组合中的组件类型。

这在需要检测组件类型的时候,使得我们不能依靠编译期的类型约束来完成,必须要在运行期间动态检测。

思考

本质

统一叶子对象和组合对象。

何时选用

  • 如果你想表示对象的部分-整体层次结构,可以选用组合模式,把整体和部分的操作统一起来,使得层次结构实现更简单,从外部来使用这个层次结构也容易。
  • 如果你希望统一地使用组合结构中的所有对象,可以选用组合模式,这正是组合模式提供的主要功能。

相关模式

  • 组合模式和装饰模式

这两个模式可以组合使用。
装饰模式在组装多个装饰器对象的时候,是一个装饰器找下一个装饰器,下一个再找下一个,如此递归下去。其实这种结构也可以使用组合模式来帮助构建,这样一来,装饰器模式就相当于组合模式的 Composite 对象了。
要让两个模式很好的组合使用,通常会让它们有一个公共的父类。因此装饰器必须支持组合模式需要的一些功能,比如增加,删除子组件等。

  • 组合模式和享元模式

这两个模式可以组合使用。
如果组合模式中出现大量相似的组件对象的话,可以考虑使用享元模式来帮助缓存组件对象,这样可以减少对内存的需要。
使用享元模式也是有条件的,如果组件对象的可变化部分能够从组件对象中分离出去,并且组件对象本身不需要向父组件发送请求的话,就可以采用享元模式。

  • 组合模式和迭代器模式

这两个模式可以组合使用。
在组合模式中,通常可以使用迭代器模式来遍历组合对象的子对象集合,而无需关心具体存放子对象的聚合结构。

  • 组合模式和访问者模式

这两个模式可以组合使用。
访问者模式能够在不修改原有对象结构的情况下,为对象结构中的对象添加新的功能,访问者模式和组合模式合用,可以把原本分散在 Composite 和 Leaf 类中的操作和行为都局部化。
如果在使用组合模式的时候,预计到今后会有添加其他功能的可能,那么可以采用访问者模式,来预留好添加新功能的方式和通道,这样以后在添加新功能的时候,就不需要在修改已有的对象结构和已经实现的功能了。

  • 组合模式和职责链模式

这两个模式可以组合使用。
职责链模式要解决的问题是:实现请求的发送者和接收者之间的接耦。职责链模式的实现方式是把多个接收者组合起来,构成职责链,然后让请求在这条链上传递,直到有接收者处理这个请求为止。
可以应用组合模式来构建这条链,相当于子组件找父组件,父组件又找父组件,如此递归下去,构成一条处理请求的组件对象链。

  • 组合模式和命令模式

这两个模式可以组合使用。
命令模式中有一个宏命名的功能,通常这个宏命令就是使用组合模式来组装出来的。