推卸责任模式,听起来确实也存在需要“推卸责任”的情况。例如,当外部请求程序进行某个处理,但程序暂时无法直接决定由哪个对象负责处理时,就需要推卸责任。这种情况下,可以将多个对象组成一条职责链,然后按照它们在职责链上的顺序一个一个地找出到底谁来负责处理。
使用该模式可以弱化“请求方”和“处理方”之间的关联关系,让双方各自都成为可独立复用的组件。此外,程序还可以应对其它需求,如根据情况不同,负责处理的对象也会发生变化的这种需求。

示例程序:

名字 说明
Trouble 表明发生的问题的类,带有问题编号
Support 用来解决问题的抽象类
NoSupport 用力解决问题的具体类(永远不处理问题)
LimitSupport 用来解决问题的具体类(仅解决编号小于指定编号的问题)
OddSupport 用来解决问题的具体类(仅解决奇数编号)
SpecialSupport 用来解决问题的具体类(仅解决指定编号的问题)
Main 制作Support的职责链,制造问题并测试程序行为

Trouble类:

表示发生问题的类。

  1. public class Trouble {
  2. private int number; // 问题编号
  3. public Trouble(int number) { // 生成问题
  4. this.number = number;
  5. }
  6. public int getNumber(){
  7. return number;
  8. } // 获取问题编号
  9. @Override
  10. public String toString() {
  11. return "Trouble{" +
  12. "number=" + number +
  13. '}';
  14. }
  15. }

Support类:

用来解决问题的抽象类,是责任链的对象。

  1. public abstract class Support {
  2. private String name; // 解决问题的实例的名字
  3. private Support next; // 要推卸给的对象
  4. public Support(String name) { // 生成解决问题的实例
  5. this.name = name;
  6. }
  7. public Support setNext(Support next) { // 设置要推卸给的对象
  8. this.next = next;
  9. return next;
  10. }
  11. public final void support(Trouble trouble){ // 解决问题的步骤
  12. if (resolve(trouble)) {
  13. done(trouble);
  14. } else if(next!=null){
  15. next.support(trouble);
  16. } else {
  17. fail(trouble);
  18. }
  19. }
  20. @Override
  21. public String toString() {
  22. return "Support{" +
  23. "name='" + name + '\'' +
  24. '}';
  25. }
  26. protected abstract boolean resolve(Trouble trouble); // 解决问题的办法
  27. protected void done(Trouble trouble){ // 成功解决问题
  28. System.out.println(trouble+"is resolved"+this+".");
  29. }
  30. protected void fail(Trouble trouble){ // 未解决
  31. System.out.println(trouble+"cannot be resolved.");
  32. }
  33. }

NoSupport类:

是Support类的子类。resolve方法总是返回false。即它是一个永远“不解决问题”的类。

  1. public class NoSupport extends Support{
  2. public NoSupport(String name) {
  3. super(name);
  4. }
  5. @Override
  6. protected boolean resolve(Trouble trouble) {
  7. return false; // 自己什么也不处理
  8. }
  9. }

LimitSupport类:

解决编号小于limit值的问题。resolve方法在判断编号小于limit值后,只是简单地返回true,但实际上这里应该是解决问题的代码。

  1. public class LimitSupport extends Support{
  2. private int limit;
  3. public LimitSupport(String name, int limit) {
  4. super(name);
  5. this.limit = limit;
  6. }
  7. @Override
  8. protected boolean resolve(Trouble trouble) {
  9. if (trouble.getNumber()<limit) {
  10. return true;
  11. }
  12. return false;
  13. }
  14. }

OddSupport类:

解决奇数编号问题。

  1. public class OddSupport extends Support{
  2. public OddSupport(String name) {
  3. super(name);
  4. }
  5. @Override
  6. protected boolean resolve(Trouble trouble) {
  7. if (trouble.getNumber()%2==1) {
  8. return true;
  9. }else{
  10. return false;
  11. }
  12. }
  13. }

SepecialSupport类:

只解决指定编号的问题,所以类中存在一个代表编号的number属性。

  1. public class SpecialSupport extends Support{
  2. private int number; // 只解决指定编号的问题
  3. public SpecialSupport(String name, int number) {
  4. super(name);
  5. this.number = number;
  6. }
  7. @Override
  8. protected boolean resolve(Trouble trouble) {
  9. if (trouble.getNumber()==number) {
  10. return true;
  11. }else{
  12. return false;
  13. }
  14. }
  15. }

Main:

  1. public class Main {
  2. public static void main(String[] args) {
  3. Support alice = new NoSupport("Alice");
  4. Support bob = new LimitSupport("Bob", 100);
  5. Support charlie = new SpecialSupport("Charlie", 429);
  6. Support diana = new LimitSupport("Diana", 200);
  7. Support elmo = new OddSupport("Elmo");
  8. LimitSupport fred = new LimitSupport("Fred", 300);
  9. // 形成职责链
  10. alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo).setNext(fred);
  11. // 制造各种问题
  12. for (int i = 0; i < 500; i+=33) {
  13. alice.support(new Trouble(i));
  14. }
  15. }
  16. }

运行结果:

  1. Trouble{number=0}is resolvedSupport{name='Bob'}.
  2. Trouble{number=33}is resolvedSupport{name='Bob'}.
  3. Trouble{number=66}is resolvedSupport{name='Bob'}.
  4. Trouble{number=99}is resolvedSupport{name='Bob'}.
  5. Trouble{number=132}is resolvedSupport{name='Diana'}.
  6. Trouble{number=165}is resolvedSupport{name='Diana'}.
  7. Trouble{number=198}is resolvedSupport{name='Diana'}.
  8. Trouble{number=231}is resolvedSupport{name='Elmo'}.
  9. Trouble{number=264}is resolvedSupport{name='Fred'}.
  10. Trouble{number=297}is resolvedSupport{name='Elmo'}.
  11. Trouble{number=330}cannot be resolved.
  12. Trouble{number=363}is resolvedSupport{name='Elmo'}.
  13. Trouble{number=396}cannot be resolved.
  14. Trouble{number=429}is resolvedSupport{name='Charlie'}.
  15. Trouble{number=462}cannot be resolved.
  16. Trouble{number=495}is resolvedSupport{name='Elmo'}.

推卸责任模式中的登场角色:

Handler(处理者):

Handler角色定义了处理请求的接口(API)。Handler角色知道“下一个处理者”是谁,如果自己无法处理请求,它会将请求转给下一个处理者。如此一直循环。

ConcreteHandler(具体的处理者):

处理请求的具体角色。

Client(请求者):

Client角色是向第一个ConcreteHandler角色发送请求的角色,在示例程序中,由Main类扮演这个角色。

拓展思路的要点:

弱化了发出请求的人和处理的人之间的关系:

推卸责任模式的最大优点就在于它弱化了发出请求的人和处理请求的人之间的关系。Client角色向第一个ConcreteHandler角色发出请求,然后请求会在职责链中传播,知道某个ConcreteHandler角色处理该请求。
如果不使用该模式,就必须有某个伟大的角色知道“谁应该处理什么请求”,这有点像类似中央集群制。而让“发出请求的人”知道“谁应该处理该请求”并不理智。因为

可以动态地改变职责链:

需要考虑负责处理各个角色之间的关系可能会发生变化的情况。如果使用推卸责任模式,通过委托推卸责任,就可以根据情况变化动态地重组职责链。

专注于自己的工作:

每个Concrete角色都专注于自己所负责的处理。当自己无法处理的时候,ConcreteHandler角色就会干脆地交给下一个处理者。这样,每个ConcreteHandler都只能处理它应该负责的请求。

推卸请求会导致处理延迟吗?

使用推卸责任模式提高了程序的灵活性,但是会导致处理延迟。与立即让相应的对象去处理请求相比,确实发生了延迟。
不过,确实需要根据实际需求权衡这两点。

类可见性:

在示例程序中的Support类中的support方法的可见性是public而resolve方法的可见性是protected的。
这是因为希望Support类接收到“解决问题”的请求后,不要使用resolve方法,而是使用support方法。
如果将resolve方法的可见性设为public。那么Support类无关的其他类就可以都可以调用resolve方法了。这样可能会导致外部对resolve方法的使用方法与Support类所期待的使用方法不符。
此外,如果这样做,可能会发生当resolve方法的名字和签名发生改变的时候,必须修改散落在程序中各个地方的代码的问题。

相关的设计模式:

Composite模式:

Handler角色会经常使用Composite模式。

Command模式:

有时会使用Command模式向Handler角色发送请求。