“推卸责任”-Chain of Respinsibility模式

87 阅读2分钟

1、引入

在公司中,我们往往可以见到这种场景:我们需要领取材料,要先找到前台,前台告诉我们要到营业窗口,而营业窗口高数我们要到售后不买,而售后部门告诉我们要到资料中心,到了资料中心我们才得以领取到材料。

而这种一级级推卸“责任”的方式,在设计模式中便被称为Chain of Respinsibility模式(责任链模式)。虽然“推卸”听起来有贬义的意思,都是实际上,这种责任推卸可以帮助我们一层层地寻找解决问题的对象。

2、示例

2.1、问题类Trouble

public class Trouble {
    private int number;

    public Trouble(int number) {
        this.number = number;
    }

    public int getNumber() {
        return number;
    }

    @Override
    public String toString() {
        return "Trouble{" +
                "number=" + number +
                '}';
    }
}

2.2、问题解决者

2.2.1、抽象父类Support

public abstract class Support {
    private String name;
    private Support next;

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

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

    public final void support(Trouble trouble) {
        if (resolve(trouble)) {
            done(trouble);
        } else if (next != null) {
            next.support(trouble);
        } else {
            fail(trouble);
        }
    }

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

    protected abstract boolean resolve(Trouble trouble);

    protected void done(Trouble trouble) {
        System.out.println(trouble+" is resolved by "+this+".");
    }

    protected void fail(Trouble trouble) {
        System.out.println(trouble+" cannot be resolved");
    }
}

2.2.2、LimitSupport

只能解决序号小于number的问题

public class LimitSupport extends Support{
    private int limit;

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

    @Override
    protected boolean resolve(Trouble trouble) {
        if (trouble.getNumber() < limit) {
            return true;
        }
        return false;
    }
}

2.2.3、NoSupport

无法解决问题

public class NoSupport extends Support{
    public NoSupport(String name) {
        super(name);
    }

    @Override
    protected boolean resolve(Trouble trouble) {
        return false;
    }
}

2.2.4、OddSupport

只能解决序号为奇数的问题

public class OddSupport extends Support{
    public OddSupport(String name) {
        super(name);
    }

    @Override
    protected boolean resolve(Trouble trouble) {
        if (trouble.getNumber() % 2 == 1) {
            return true;
        }
        return false;
    }
}

2.2.5、SpecialSupport

只能解决特定序号问题

public class SpecialSupport extends Support{
    private int number;

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

    @Override
    protected boolean resolve(Trouble trouble) {
        if (trouble.getNumber() == number) {
            return true;
        }
        return false;
    }
}

2.3、测试

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", 411);
        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));
        }
    }
}

运行结果:

image.png

3、tips

  • 值得注意的是Support类的support()方法被设置为final,代表不可重写,这个final的意义是显著的,如果子类更改了该方法,容易导致调用链断裂。

  • 调用链的思想在许多框架、系统内有使用:

    • mybatis-plus中:
    queryWrapper.like(StringUtils.isNotBlank(name), User::getName, name).
            ge(beginAge!=null,User::getAge,beginAge).
            le(endAge!=null,User::getAge,endAge);
    
    • 视窗系统中的点击功能,有时在上层按键(或其他)不能解决点击事件时,会将事件交给下层。
  • 调用链模式可以通过推卸请求的方式来寻找解决者,然而有时调用链过长会导致较长的延迟,这是在设计时需要权衡的问题。