责任链模式

Java基础

浏览数:58

2019-8-21

责任链模式

一、概念

是一个请求有多个对象来处理,这些对象是一条链,但具体由哪个对象来处理,根据条件判断来确定,如果不能处理会传递给该链中的下一个对象,直到有对象处理它为止。

二、使用场景

  • 有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定。
  • 在不明确指定接受者的情况下,向多个对象中的一个提交一个请求。
  • 可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求。

三、UML结构图

将接收者对象连成一条链,并在该链上传递请求,直到有一个接收者对象处理它。通过让更多对象有机会处理请求,避免了请求发送者和接收者之间的耦合。

责任链模式UML.png

四、代码示例

案例一:公司处理员工请假请求

Handler:

public abstract class Handler {
    
    private Handler nextHandler;
    
    //当前领导能审批通过的最多天数
    public int maxDay;
    
    protected Handler(int maxDay){
        this.maxDay = maxDay;
    }
    
    //设置责任链中下一个处理请求的对象
    public void setNextHandler(Handler handler){
        nextHandler = handler;
    }
    protected void handleRequest(int day){
        if(day <= maxDay){
            reply(day);
        }else{
            if(nextHandler != null){
                //审批权限不够,继续上班
                nextHandler.handleRequest(day);
            }else{
                System.out.println("没有更高的领导审批了");
            }
        }
    }
    //交由具体的handler来实现
    protected abstract void reply(int day);

}

ProjectManager:

public class ProjectManager extends Handler{

    public ProjectManager(int maxDay) {
        super(maxDay);
    }

    @Override
    protected void reply(int day) {
        System.out.println(day+"天请假,项目经理直接审批通过");
    }
}

案例二:销售团队处理客户需求

销售团队的层级关系:

Sales:<=5%
Manager:<=30%
Director:<=40%
Vice President:<=50%
CEO:<=55%

PriceHandler:

/**
 * 价格处理人:负责处理客户的折扣申请
 * 使用抽象类作为Handler的载体,
 * 因为Handler需要有一个指向自身类型的引用,使用interface不方便
 * @author HCX
 *
 */
public abstract class PriceHandler {
    
    /**
     * 直接后继,用于传递请求
     * 指向自身类型的引用
     * protected:使子类都可以访问到
     */
    protected PriceHandler successor;
    
    public void setSuccessor(PriceHandler successor) {
        this.successor = successor;
    }
    
    /**
     * 处理折扣请求
     * @param discount
     */
    public abstract void processDiscount(float discount);

    /**
     * 创建PriceHandler的工厂方法
     * @return
     */
    public static PriceHandler createPriceHandler() {
        PriceHandler sales = new Sales();
        PriceHandler manager = new Manager();
        PriceHandler director = new Director();
        PriceHandler vicePresident = new VicePresident();
        PriceHandler ceo = new CEO();
        
        //设置直接后继
        sales.setSuccessor(manager);
        manager.setSuccessor(director);
        director.setSuccessor(vicePresident);
        vicePresident.setSuccessor(ceo);
        return sales;
    }
    
}

Sales:

/**
 * 销售人员,可以批准5%以内的折扣
 * @author HCX
 *
 */
public class Sales extends PriceHandler {

    @Override
    public void processDiscount(float discount) {
        if(discount <= 0.05){
            System.out.format("%s批准了折扣:%.2f%n", this.getClass().getName(),discount);
        }else{//让直接后继来处理
            successor.processDiscount(discount);
        }
    }

}

Manager:

/**
 * 销售经理,可以批准30%以内的折扣
 * @author HCX
 *
 */
public class Manager extends PriceHandler{

    @Override
    public void processDiscount(float discount) {
        if(discount <= 0.3){
            System.out.format("%s批准了折扣:%.2f%n", this.getClass().getName(),discount);
        }else{//超过直接传递给直接后继
            successor.processDiscount(discount);
        }
        
    }

}

Director:

/**
 * 销售总监,可以批准40%以内的折扣
 * @author HCX
 *
 */
public class Director extends PriceHandler {

    @Override
    public void processDiscount(float discount) {
        if(discount <= 0.4){
            System.out.format("%s批准了折扣:%.2f%n", this.getClass().getName(),discount);
        }else{//超过直接传递给直接后继
            successor.processDiscount(discount);
        }
        
    }

}

VicePresident:

/**
 * 销售副总裁,可以批准50%以内的折扣
 * @author HCX
 *
 */
public class VicePresident extends PriceHandler {

    @Override
    public void processDiscount(float discount) {
        if(discount <= 0.5){
            System.out.format("%s批准了折扣:%.2f%n", this.getClass().getName(),discount);
        }else{//超过直接传递给直接后继
            successor.processDiscount(discount);
        }
    }

}

CEO:

/**
 * CEO,可以批准55%以内的折扣
 * 折扣超出55%,拒绝申请
 * @author HCX
 *
 */
public class CEO extends PriceHandler{

    @Override
    public void processDiscount(float discount) {
        if(discount <= 0.55){
            System.out.format("%s批准了折扣:%.2f%n", this.getClass().getName(),discount);
        }else{//让直接后继来处理
            System.out.format("%s拒绝了折扣:%.2f%n", this.getClass().getName(),discount);
        }
        
    }

}

Customer:

/**
 * 客户,请求折扣
 * @author HCX
 *
 */
public class Customer {
    
    private PriceHandler priceHandler;
    
    public void setPriceHandler(PriceHandler priceHandler) {
        this.priceHandler = priceHandler;
    }

    //只关心折扣请求是否被处理了,不关心被谁处理的。
    public void requestDiscount(float discount){
         priceHandler.processDiscount(discount);
     }
    
    public static void main(String[] args) {
        Customer customer = new Customer();
        customer.setPriceHandler(PriceHandler.createPriceHandler());
        
        Random random = new Random();
        
        for(int i=1;i<100;i++){
            System.out.println(i+":");
            customer.requestDiscount(random.nextFloat());
        }
    }

}

修改:在Sales和manager之间加入Lead层级:
加入了新的类Lead,并对工厂方法进行了改动

Lead:

/**
 * 销售小组长,可以批准15%以内的折扣
 * @author HCX
 *
 */
public class Lead extends PriceHandler{

    @Override
    public void processDiscount(float discount) {
        if(discount <= 0.15){
            System.out.format("%s批准了折扣:%.2f%n", this.getClass().getName(),discount);
        }else{//超过直接传递给直接后继
            successor.processDiscount(discount);
        }
        
    }

}

PriceHandler:

/**
 * 价格处理人:负责处理客户的折扣申请
 * 使用抽象类作为Handler的载体,
 * 因为Handler需要有一个指向自身类型的引用,使用interface不方便
 * @author HCX
 *
 */
public abstract class PriceHandler {
    
    /**
     * 直接后继,用于传递请求
     * 指向自身类型的引用
     * protected:使子类都可以访问到
     */
    protected PriceHandler successor;
    
    public void setSuccessor(PriceHandler successor) {
        this.successor = successor;
    }
    
    /**
     * 处理折扣请求
     * @param discount
     */
    public abstract void processDiscount(float discount);

    /**
     * 创建PriceHandler的工厂方法
     * @return
     */
    public static PriceHandler createPriceHandler() {
        PriceHandler sales = new Sales();
        PriceHandler lead = new Lead();
        PriceHandler manager = new Manager();
        PriceHandler director = new Director();
        PriceHandler vicePresident = new VicePresident();
        PriceHandler ceo = new CEO();
        
        //设置直接后继
        sales.setSuccessor(lead);
        lead.setSuccessor(manager);
        manager.setSuccessor(director);
        director.setSuccessor(vicePresident);
        vicePresident.setSuccessor(ceo);
        return sales;
    }
    
}

改进:OO之中的原则:单一职责原则,在设计一个接口时,应该只将与该接口业务相关的方法放在接口之中,这样才能使设计更加健壮而不至于当变化发生时,需要修改多处。

PriceHandler:提供了处理业务的方法,也提供了创建PriceHandler实例的工厂方法。这两种方法在功能上不能类聚的,两者之间没有关系,该设计不符合单一职责原则。
命名规范不符合见名知意的原则。

把工厂方法抽取出来:

PriceHandlerFactory:

public class PriceHandlerFactory {

    /**
     * 创建PriceHandler的工厂方法
     * @return
     */
    public static PriceHandler createPriceHandler() {
        PriceHandler sales = new Sales();
        PriceHandler lead = new Lead();
        PriceHandler manager = new Manager();
        PriceHandler director = new Director();
        PriceHandler vicePresident = new VicePresident();
        PriceHandler ceo = new CEO();
        
        //设置直接后继
        sales.setSuccessor(lead);
        lead.setSuccessor(manager);
        manager.setSuccessor(director);
        director.setSuccessor(vicePresident);
        vicePresident.setSuccessor(ceo);
        return sales;
    }

}

Customer:

/**
 * 客户,请求折扣
 * @author HCX
 *
 */
public class Customer {
    
    private PriceHandler priceHandler;
    
    public void setPriceHandler(PriceHandler priceHandler) {
        this.priceHandler = priceHandler;
    }

    //只关心折扣请求是否被处理了,不关心被谁处理的。
    public void requestDiscount(float discount){
         priceHandler.processDiscount(discount);
     }
    
    public static void main(String[] args) {
        Customer customer = new Customer();
        customer.setPriceHandler(PriceHandlerFactory.createPriceHandler());
        
        Random random = new Random();
        
        for(int i=1;i<100;i++){
            System.out.println(i+":");
            customer.requestDiscount(random.nextFloat());
        }
    }

}

PriceHandler:

/**
 * 价格处理人:负责处理客户的折扣申请
 * 使用抽象类作为Handler的载体,
 * 因为Handler需要有一个指向自身类型的引用,使用interface不方便
 * @author HCX
 *
 */
public abstract class PriceHandler {
    
    /**
     * 直接后继,用于传递请求
     * 指向自身类型的引用
     * protected:使子类都可以访问到
     */
    protected PriceHandler successor;
    
    public void setSuccessor(PriceHandler successor) {
        this.successor = successor;
    }
    
    /**
     * 处理折扣请求
     * @param discount
     */
    public abstract void processDiscount(float discount);
    
}

改进之后,Customer类只依赖于PriceHandler和PriceHandlerFactory两个类,并没有依赖实现的PriceHandler:Sales和Manager等;因此耦合度较低。

五、在实际中的应用

①try-catch语句:
每一个catch语句是根据Exception异常类型进行匹配的,一般会有多个catch语句,就形成了一个责任链;此时如果有一个catch语句与当前所要处理的异常Exception符合时,该Exception就会交给相应的catch语句进行处理,之后的catch语句就不会再执行了。

②异常处理机制:
方法的调用构成了一个栈,当栈顶的方法产生异常时,需要将异常抛出,被抛出的异常沿着调用栈向下发展,寻找一个处理的块,被栈顶抛出的方法作为一个请求,而调用栈上的每一个方法就相当于一个handler,该Handler即可以选择自行处理这个被抛出的异常,也可以选择将异常沿着调用栈传递下去。
异常:请求
调用栈中的每一级:Handler
调用栈中的handler:责任链
栈底元素:上一级元素的直接后继

③过滤器链(一般链条中只有一个对象处理请求,但是过滤器链可以有多个对象同时处理请求)

过滤器链.png

④Spring Security框架
通过多个filter类构成一个链条来处理Http请求,从而为应用提供一个认证与授权的框架。

六、责任链模式内部处理

在责任链模式中,作为请求接受者的多个对象通过对其后继的引用而连接起来形成一条链。请求在这条链上传递,直到链上某一个接收者处理这个请求。每个接收者都可以选择自行处理请求或是向后继传递请求

Handler设了一个自身类型的对象作为其后继,Handler是抽象的,从而整条链也是抽象的,这种抽象的特性使得在运行时可以动态的绑定链条中的对象,从而提供了足够的空间。

在代码中直接后继successor的类型是PriceHandler,而不是任何其他具体的类(Sales、Manager等),使得在后面变更需求时,加入了lead层次,可以简单的实现。所以责任链模式遵循了OO中的依赖倒置原则,即依赖于抽象而非依赖于具体。降低了程序的耦合度。

发出请求的客户端并不知道链上的哪一个接收者会处理这个请求,从而实现了客户端和接收者之间的解耦。

七、责任链模式的优缺点

  • 开闭原则:对扩展开放,对变更关闭;
    有业务变更时,希望通过新增一个类,而非修改原有的代码来满足业务需求。
    在案例二中,通过新增了一个lead类,但同时也修改了工厂方法createPriceHandler。
    而实际的好坏还是取决于实际的项目需求。因此对经典原则的取舍需要根据实际项目情况决定。
  • 执行性能:在结构上,责任链模式由处理器首尾相接构成的一条链,当由请求到来之时,需要从链的头部开始遍历整条责任链,直到有一个处理器处理了请求,或者是整个链条遍历完成。在这个过程中性能的损耗体现在两个方面:
    时间:相对于单个handler处理请求的时间而言,整个链条遍历的过程可能会消耗更多的时间。
    内存:创建了大量的对象来表示处理器对象,但是实际仅仅使用了其中的少部分,剩余的大部分处理器都未被使用到。

说明:本文部分内容来自慕课网。

作者:JS_HCX