设计模式拾荒之责任链模式

  1. 参考书籍: 《Design Patterns: Elements of Reusable Object-Oriented Software》

设计模式用前须知

  • 设计模式种一句出现频率非常高的话是,“ 在不改动。。。。的情况下, 实现。。。。的扩展“ 。
  • 对于设计模式的学习者来说,充分思考这句话其实非常重要, 因为这句往往只对框架/ 工具包的设计才有真正的意义。因为框架和工具包存在的意义,就是为了让其他的程序员予以利用, 进行功能的扩展,而这种功能的扩展必须以不需要改动框架和工具包中代码为前提
  • 对于应用程序的编写者, 从理论上来说, 所有的应用层级代码至少都是处于可编辑范围内的, 如果不细加考量, 就盲目使用较为复杂的设计模式, 反而会得不偿失, 毕竟灵活性的获得, 也是有代价的。

责任链模式(Chain of Responsibility Pattern)

  • 设计意图

    • GoF: 使一个请求有机会被不止一个对象处理, 从而避免请求的发送者和请求的接受者的耦合。 把能够处理请求的对象串成一个链条, 然后把请求沿着链条依次传递, 直到一个对象处理它。
    • 关键词: “链” “一个请求有机会被不止一个对象处理”
  • GoF 举例

    • 这个例子可能对大部分人都不是很熟悉, 在有一些应用中, 帮助信息会以这样一种方式提供, 点击帮助按钮后, 光标会变携带问号, 然后用户可以在想要获取帮助的图形组件(例如:按钮) 上点击一下, 就会弹出对这个按钮的功用说明。
      • 现在这个功能好像不太常见了, 印象中老版本的 windows 中好像比较容易见到这个功能, 但是win8 中鼠标指针设置 的地方依然可以看到这种光标, 为了方便描, 下文中都称其为“帮助光标”
    • 注意到, 被帮助光标点击到的图形组件会触发一个特殊的点击事件, 这个事件是一个获取帮助信息的请求, 虽然它的触发者是一个按钮组件, 但是我们却不能为它提供一个通用的事件处理器, 因为帮助信息和按钮所处的上下文有关, 例如一个确认按钮在不同的对话框中所执行的功能肯定是不一样的。 此外, 当被帮助光标点击到的组件没有合适的帮助信息提供给用户时,我们会希望提供一个更为一般化, 但却依然和该组件上下文有关的帮助信息, 例如: 当一个按钮没有具体的帮助信息时, 我们希望当帮助光标点击这个按钮时, 可以弹出这个按钮所在对话框或窗口的帮助信息。
    • 所以, 这里所面临的问题是, 触发帮助请求的对象(被帮助光标所点击的按钮)并不能确定最终提供帮助信息的对象是什么。
      • 读者可能会疑惑, 编程人员肯定知道界面上某个按钮最终提供怎样的帮助信息, 直接对不同的按钮绑定不同的帮助处理信息不就可以了吗。
      • 这里的关键依旧在于, 我们需要从图形界面的编写框架来思考, 回想一下自己编写界面时的习惯, 都是添加对应的事件处理函数, 然后与界面组件绑定, 那么对于帮助信息的处理, 我们所习惯的方式也必然是
        // 这里仅仅以java 的方式举例, 实际上并没有helpClicked 这个函数
        bt.addActionListener(new ActionListener()
        {
            public void helpClicked(HelpEvent e)
            {
                 System.out.println("按钮的帮助信息");
                 System.exit(0);
            }
        });
  • 那么对于我们编程人员来说, 在某一对话框上添加了 helpClicked() 后, 对于该对话框中可能存在的多个按钮, 我们可能仅仅会选择一部分按钮添加更为具体的帮助信息, 对于那些我们没有添加 helpClicked() 的按钮, 在它被点击到的时候, 我们希望的是该按钮可以自动将该请求发送给它所属于的对话框, 由对话框的 helpClicked() 进行处理。

  • 解决方案

    • 利用责任链模式解耦请求的发送者和接受者, 给一个请求被不止一个对象处理的机会。 这个请求会沿着链条上的对象依次传递, 直到找到一个能够处理该请求的对象。

    • 链条中的第一个对象接收请求,要么处理它, 要么将其转发给链条上的下一个候选人, 下一个候选人则继续以同样的方式处理。 在这种情况下, 这个发起请求的对象并不明确地知道最终处理请求的对象是什么。

    • 现假设用户用帮助光标点击了“Print” 按钮 。 这个按钮是被包含在一个打印对话框 PrintDialog 中的, 这个PrintDialog 知道自己属于打印应用模块 PrintApplication , 那么最终由 PrintApplication 提供一个打印功能的整体说明的处理流畅就是如下的方式。 (根据上文提供的关系图可以发现, 如果点击 打印对话框 PrintDialog 中的 ok 按钮, 会弹出相同的帮助信息)

    • 这个例子所对应的类结构图如下

    • 链条上的每一个对象类型 Button, Dialog, Application 都共享了相同的接口 HelpHandler , 从而具备处理帮助请求的能力和将请求转发给链条后继者的能力 。

应用场景

在如下情况时 ,使用责任链模式

  • 不止一个对象可能处理一个请求, 且发起请求的对象可能并不直接持有最终处理该请求对象的引用。 处理请求的对象需要被自动地查询出来。
  • 你想把一个请求发给多个对象中的某一个,但却不需要指定具体是哪一个。
  • 能够处理请求的对象应该被动态地(运行时)指定。

泛化结构图

  • 注意点一: 在具体的实现中, 责任链模式可以借助对象中已经存在的引用关系来完成请求的转发。 而不一定需要按照结构图中的方式重新定义一套接口或类的关系。
  • 注意点二: 假如已经存在的对象关系中, 没有现成的链式引用可以满足应用的需求, 在按照结构图中的方式定义类关系时, Handler 需要定义链的后继者 sucessor, 且对其子类提供后继者的管理接口例如 setSuccessor。handleRequest ()方法应当提供一个默认实现, 就是将请求直接转发给后继者, 这样 Handler 的子类如果不重写 handleRequest , 请求会被默认转发给它的后继者。

总结

  • 责任链的好处一: 是在某些情况下可以简化对象之间的引用关系。 采用了责任链模式后, 发送请求的对象不再需要维护所有可能处理请求的对象引用, 也不需要自己直接判断请求应该被哪个对象最终处理, 每个对象只需要维护单一的责任链后继对象的引用即可。
  • 责任链的好处二:由于对象的引用可以在运行时修改, 所以请求的处理者可以在运行时动态添加和变化。 这种方式同样可以和静态绑定的方式结合使用, 例如静态地确定一个请求默认由哪些类处理,运行时刻动态地为一些特殊请求添加特殊的处理对象。 

你可能感兴趣的:(设计模式)