设计模式学习(十三):Chain of Responsibility责任链模式

一、什么是Chain of Responsibility模式

        先用一句话来概括:Chain of Responsibility模式就是推卸责任

        我们首先看看什么是推卸责任。假设现在我们要去公司领取资料。首先我们向公司前台打听要去哪里领取资料,她告诉我们应该去“营业窗口”。然后等我们到了“营业窗口”后,又被告知应该去“售后部门”。等我们好不容易赶到了“售后部门”,又被告知应该去“资料中心”,因此最后我们又不得不赶往“资料中心"。像这样,在找到合适的办事人之前,我们被不断地踢给一个又一个人,这就是“推卸责任”。

        “推卸责任”听起来有些贬义的意思,但是有时候也确实存在需要“推卸责任”的情况。例如,当外部请求程序进行某个处理,但程序暂时无法直接决定由哪个对象负责处理时,就需要推卸责任。这种情况下,我们可以考虑将多个对象组成一条职责链,然后按照它们在职责链上的顺序一个一个地找出到底应该谁来负责处理

        这种模式被称为Chain of Responsibility模式。我们可以将它想象为推卸责任的结构,这有利于大家记住这种模式。

        使用Chain of Responsibility模式可以弱化“请求方”和“处理方”之间的关联关系,让双方各自都成为可独立复用的组件。此外,程序还可以应对其他需求,如根据情况不同,负责处理的对象也会发生变化的这种需求。

        当一个人被要求做什么事情时,如果他可以做就自己做,如果不能做就将“要求”转给另外一个人。下一个人如果可以自己处理,就自己做;如果也不能自己处理,就再转给另外一个人……这就是Chain of Responsibility模式。

设计模式学习(十三):Chain of Responsibility责任链模式_第1张图片

设计模式学习(十三):Chain of Responsibility责任链模式_第2张图片
二、Chain of Responsibility模式示例代码

 

2.1 各个类之间的关系

        各个类的功能: 

设计模式学习(十三):Chain of Responsibility责任链模式_第3张图片

        类图:

设计模式学习(十三):Chain of Responsibility责任链模式_第4张图片

 2.2 Trouble类

        Trouble类是表示发生的问题的类。

/**
 * 问题类
 */
public class Trouble {
    // 问题编号
    private int number;      
    
    public Trouble(int number) {    
        this.number = number;
    }
    
    // 获取问题编号
    public int getNumber() {        
        return number;
    }
    public String toString() {
        return "[Trouble " + number + "]";
    }
}

2.3 Support类

        Support类是用来解决问题的抽象类,它是职责链上的对象。

        resolve方法是需要子类去实现的抽象方法。如果resolve返回true,则表示问题已经被处理,如果返回false则表示问题还没有被处理(即需要被推卸给下一个对象)。

        support方法会调用resolve方法,如果resolve方法返回false,则support方法会将问题转交给下一个对象。如果已经到达职责链中的最后一个对象,则表示没有人处理问题,将会显示出处理失败的相关信息。在本例中我们只是简单地输出处理失败的相关信息,但根据需求不同,有时候也需要抛出异常。

public abstract class Support {
    // 解决问题的实例的名字
    private String name;
    // 要推卸给的对象
    private Support next;

    public Support(String name) {
        this.name = name;
    }

    /**
     * 解决问题的步骤(递归写法)
     * @param trouble 问题
     */
    public void support(Trouble trouble) {
        if (resolve(trouble)) {
            done(trouble);
        } else if (next != null) {
            next.support(trouble);
        } else {
            fail(trouble);
        }
    }
    
//    //也可以用循环写法
//    public void support(Trouble trouble) {
//        for (Support obj = this; true; obj = obj.next) {
//            if (obj.resolve(trouble)) {
//                obj.done(trouble);
//                break;
//            } else if (obj.next == null) {
//                obj.fail(trouble);
//                break;
//            }
//        }
//    }

    /**
     * 解决问题的方法
     * @param trouble 问题
     * @return 是否被解决
     */
    protected abstract boolean resolve(Trouble trouble);

    /**
     * 表示已解决
     * @param trouble 问题
     */
    protected void done(Trouble trouble) {
        System.out.println(trouble + " is resolved by " + this + ".");
    }

    /**
     * 表示未解决
     * @param trouble 问题
     */
    protected void fail(Trouble trouble) {
        System.out.println(trouble + " cannot be resolved.");
    }

    public Support setNext(Support next) {
        this.next = next;
        return next;
    }

    public String toString() {
        return "[" + name + "]";
    }
}

        偷偷地告诉大家,support方法调用了抽象方法resolve,它属于Template Method模式:设计模式学习(六):Template Method模板方法模式_玉面大蛟龙的博客-CSDN博客

2.4 Nosupport类

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

/**
 * 永远不解决的问题的类
 */
public class NoSupport extends Support {
    public NoSupport(String name) {
        super(name);
    }

    /**
     * 解决问题的方法
     * @param trouble 问题
     * @return false,表示不解决
     */
    protected boolean resolve(Trouble trouble) {     
        return false;
    }
}

 2.5 LimitSupport类

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

/**
 * 解决编号小于limit值的问题
 */
public class LimitSupport extends Support {
    // 可以解决编号小于limit的问题
    private int limit;

    public LimitSupport(String name, int limit) {
        super(name);
        this.limit = limit;
    }

    /**
     * 解决问题的方法
     * @param trouble 问题
     * @return 是否被解决
     */
    protected boolean resolve(Trouble trouble) {
        if (trouble.getNumber() < limit) {
            //这里其实应该是具体的解决问题的代码,为了方便就直接返回true了
            return true;
        } else {
            return false;
        }
    }
}

2.6 OddSupport类

        OddSupport类解决奇数编号的问题。

/**
 * 解决奇数编号的问题
 */
public class OddSupport extends Support {
    public OddSupport(String name) {                
        super(name);
    }

    /**
     * 解决问题的方法
     * @param trouble 问题
     * @return 是否解决
     */
    protected boolean resolve(Trouble trouble) {    
        if (trouble.getNumber() % 2 == 1) {
            // 这里其实应该是具体的解决问题的代码,为了简单就直接返回true了
            return true;
        } else {
            return false;
        }
    }
}

2.7 SpecialSupport类

        SpecialSupport类解决指定编号的问题。

/**
 * 解决指定编号的问题
 */
public class SpecialSupport extends Support {
    // 编号
    private int number;

    public SpecialSupport(String name, int number) {
        super(name);
        this.number = number;
    }

    /**
     * 解决问题的方法
     * @param trouble 问题
     * @return 是否解决
     */
    protected boolean resolve(Trouble trouble) {
        if (trouble.getNumber() == number) {
            // 这里其实应该是具体的解决问题的代码,为了简单就直接返回true了
            return true;
        } else {
            return false;
        }
    }
}

2.8 用于测试的Main类 

        Main类首先生成了Alice至Fred等6个解决问题的实例,就好像是前言中公司的六个员工一样。虽然此处定义的变量都是Support类型的,但是实际上所保存的变量却是NoSupport、Limitsupprot,Specialsupport,Oddsupport等各个类的实例,这样这六个员工都负责处理不同的问题。

        接下来,Main类调用setNext方法将Alice至Fred这6个实例串联在职责链上。之后,Main类逐个生成问题,并将它们传递给alice,然后显示最终谁解决了该问题。请注意,这里的问题编号从0开始,增长步长为33。这里的33并没有什么特别的意思,我们只是随便使用一个增长步长使程序更有趣而已。

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

 2.9 运行结果

        最开始Bob非常努力地解决了几个问题,当他无法解决的时候会将问题交给Diana负责。在运行结果中,完全没有出现Alice 的身影,这是因为Alice会把所有的问题推给别人。当问题编号超过300后,不论是哪个LimitSupport类的实例都无法解决了。不过,只要编号为奇数,OddSupport类的实例Elmo就可以帮我们解决问题。而SpecialSupport类的实例charlie只负责解决编号为429的问题,因此在运行结果中它只出现了一次。

        下图展示了解决编号为363号问题时的时序图。在该时序图中,我们重点关注了support方法的调用情况。实际上,每个Support 在调用下一个Support的support方法之前,都会先调用自身的resolve方法。 

[Trouble 0] is resolved by [Bob].
[Trouble 33] is resolved by [Bob].
[Trouble 66] is resolved by [Bob].
[Trouble 99] is resolved by [Bob].
[Trouble 132] is resolved by [Diana].
[Trouble 165] is resolved by [Diana].
[Trouble 198] is resolved by [Diana].
[Trouble 231] is resolved by [Elmo].
[Trouble 264] is resolved by [Fred].
[Trouble 297] is resolved by [Elmo].
[Trouble 330] cannot be resolved.
[Trouble 363] is resolved by [Elmo].
[Trouble 396] cannot be resolved.
[Trouble 429] is resolved by [Charlie].
[Trouble 462] cannot be resolved.
[Trouble 495] is resolved by [Elmo].

设计模式学习(十三):Chain of Responsibility责任链模式_第5张图片

三、拓展思路的要点

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

        Chain of Responsibility 模式的最大优点就在于它弱化了发出请求的人(Client角色)和处理请求的人(ConcreteHandler角色)之间的关系。Client 角色向第一个ConcreteHandler角色发出请求,然后请求会在职责链中传播,直到某个ConcreteHandler 角色处理该请求。

        如果不使用该模式,就必须有某个伟大的角色知道“谁应该处理什么请求”,这有点类似中央集权制。而让“发出请求的人”知道“谁应该处理该请求”并不明智,因为如果发出请求的人不得不知道处理请求的人各自的责任分担情况,就会降低其作为可复用的组件的独立性。

        为了简单起见,在示例程序中,我们让扮演Client角色的Main类负责串联起ConcreteHandler的职责链。

3.2 可以动态地改变职责链

        在示例程序中,问题的解决是按照从Alice到Fred的固定顺序进行处理的。但是,我们还需要考虑负责处理的各个ConcreteHandler角色之间的关系可能会发生变化的情况。如果使用Chain of Responsibility模式,通过委托推卸责任,就可以根据情况变化动态地重组职责链。

        如果不使用Chain of Responsibility模式,而是在程序中固定写明“某个请求需要谁处理”这样的对应关系,那么很难在程序运行中去改变请求的处理者。
在视窗系统中,用户有时需要可以自由地在视窗中添加控件(按钮和文本输入框等)。这时,Chain of Responsibility模式就有了用武之地。

3.3 专注于自己的工作

        “推卸”这个词虽然有贬义,但是反过来想,这样才可以使每个对象更专注于自己的工作,即每个ConcreteHandler角色都专注于自己所负责的处理。当自己无法处理时,ConcreteHandler角色就会干脆地将请求转给下一个ConcreteHandler。这样,每个ConcreteHandler角色就能只处理它应该负责的请求了。

        如果我们不使用Chain of Responsibility 模式又会怎样呢?这时,我们需要编写一个“决定谁应该负责什么样的处理”的方法。亦或是让每个ConcreteHandler角色自己负责“任务分配”工作,即“如果自己不能处理,就转交给那个人。如果他也不能处理,那就根据系统情况将请求再转交给另外一个人”。

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

        使用Chain of Responsibility 模式可以推卸请求,直至找到合适的处理请求的对象,这样确实提高了程序的灵活性,但是难道不会导致处理延迟吗?

        确实如此,与“事先确定哪个对象负责什么样的处理,当接收到请求时,立即让相应的对象去处理请求”相比,使用Chain of Responsibility模式确实导致处理请求发生了延迟。

        不过,这是一个需要权衡的问题。如果请求和处理者之间的关系是确定的,而且需要非常快的处理速度时,不使用Chain of Responsibility模式会更好。


 

你可能感兴趣的:(架构,设计模式,责任链模式,java,后端)