天天看点

设计模式系列(二): 组合模式意图动机适用性结构参与者协作效果实现折衷

意图

以树形结构表示“整体-部分”的层次结构。对单个对象和组合对象拥有一致性的操作体验。

动机

在一些场景下,用户可以使用简单的组件组合复杂的组件,这些复杂的组件又可以组合成更复杂的组件。最简单的实现方法,是对于简单组件分别使用一些类实现,然后再定义一些类,作为这些简单类的容器。

这存在一个明显的问题:使用这些类的代码要区别简单组件和容器,而实际上大多数使用情况下,用户认为他们应用有一致的行为。加入这些区别,使得操作复杂化。

组合(Composite)模式,描述了如何使用递归组合,使用户不需要区分这些类。

组合模式的关键在于一个抽象的类,即可以代表简单组件,又可以代表容器。所有的组件和容器都继承自此抽象类,同时容器类又是基类对象的聚合。

适用性

  • 表示对象的部分-整体层次结构
  • 希望用户忽略组合对象与简单组件的区别

结构

  • 类图
    设计模式系列(二): 组合模式意图动机适用性结构参与者协作效果实现折衷
  • 结构图
    设计模式系列(二): 组合模式意图动机适用性结构参与者协作效果实现折衷

参与者

  • Component
    • 为组合中的对象声明接口
    • 实现所有类的默认行为
    • 声明一组接口用于访问和管理 Component的子组件
    • (可选)在递归结构中定义一个访问父组件的接口,并实现
  • Leaf
    • 表示叶子节点对象,没有子节点
    • 定义叶子节点的行为
  • Composite
    • 定义有子组件的组件的行为
    • 存储子组件
    • 实现 Component 中有关于子组件的接口
  • Client
    • 通过 Component 的接口操作组合组件的对象

协作

Client 使用 Component 的接口与组合结构中的对象交互,叶子节点直接处理请求。Composite 节点通常会转发请求给子节点,转发前后可能会有一些辅助操作

效果

  • 定义了包含基本组件和组合组件的类层次结构
  • 简化了客户端代码
  • 更容易的添加新类型组件
  • 设计变得更一变化

实现折衷

  1. 显式的父组件引用(parent reference)
    • 优势

      管理组合结构简单化,方便于结构上移和删除操作。支持 Chain of Responsibility 模式

    • 是否是现在 Component 中

      如果能够保证父引用的不变式,则可以。这表明,任何一个组件,他的父组件引用所指向的组件,必须将其视为子组件。

      最简单的实现方法是,只有在一个组件从一个Composite中添加或移除时,才改变其父引用

  2. 共享组件
    • 优势

      节省存储空间

    • 难点

      组件有多个父组件时,问题变得棘手。解决方法是 Flyweight 模式

  3. 最大化组件接口
    • 优势

      使得客户端能够忽略不同组件的区别

    • 劣势

      违反了基类设计原则,有很多操作Leaf用不到

  4. 何处声明子组件管理操作
    • Component

      带来透明性(transparency), 丧失安全性(safety)。因为client可能会访问叶子节点的这些操作

    • Composite

      带来安全性,丧失透明性。任何对叶子节点的操作,导致便以失败。但是,客户端使用时要区分是Leaf还是Composite。

    • 总结

      组合模式更青睐于透明性。对于安全性的保证可以引入函数

      Composite* GetComposite()

      ,当时一个Leaf时,返回空指针。

      真正的便捷,可以通过定义通用的

      Add()

      Remove()

      接口来解决。默认行为直接返回失败信息,而Composite通过重写这些函数达到应有的效果。
  5. 在Component中放入Component列表
    • 优势

      便于管理子组件

    • 劣势

      浪费空间

    • 折衷

      只有在Leaf少的时候,这么做才合适

  6. 子组件有序

    需要有序时,使用Iterator模式

  7. 缓存

    缓存能够提升遍历时的性能。但要注意,在节点改变时,通知父节点缓存已经失效。

  8. 删除组件责任

    父组件在自身被删除时,应负责删除自己的子组件。除非子组件是不可变的或者共享的

  9. 存储组件的最佳数据结构

    依据不同场景灵活变化。有时甚至需要自己实现一些管理接口和结构。可以参见Interpreter模式

继续阅读