【设计模式系列12】责任链模式原理和示例及其在Spring源码中的运用

设计模式系列总览

设计模式飞机票
三大工厂模式登机入口
策略模式登机入口
委派模式登机入口
模板方法模式登机入口
观察者模式登机入口
单例模式登机入口
原型模式登机入口
代理模式登机入口
装饰者模式登机入口
适配器模式登机入口
建造者模式登机入口
责任链模式登机入口
享元模式登机入口
组合模式登机入口
门面模式登机入口
桥接模式登机入口
中介者模式登机入口
迭代器模式登机入口
状态模式登机入口
解释器模式登机入口
备忘录模式登机入口
命令模式登机入口
访问者模式登机入口
软件设计7大原则和设计模式总结登机入口

前言

上一篇,我们介绍了建造者模式,以及建造者模式在源码中的运用,今天我们会先介绍一下责任链模式,然后会再通过一个示例来将责任链模式和建造者模式结合起来应用。

什么是责任链模式

责任链模式(Chain of Responsibility Pattern)是指将链中的每一个节点看作是一个对象,每个节点处理的请求均不同,且每个节点内部自动维护了一个下一个节点对象。当一个请求在链路的头部发出时,会沿着链的路径依次传递给每一个节点对象,直到有对象处理这个请求为止。

责任链模式属于行为型模式。

写法示例

Talk is cheap,Show me the code。我们就以一个登录校验账号密码,角色,权限等信息的功能为例,直接来看一下责任链模式是怎么写的。

登录用户信息类

首先我们创建一个登录用户信息类:

package com.zwx.design.pattern.chainOfResponsibility;

public class LoginUser {
    private String loginName;
    private String password;
    private String roleName;
    private String permission;

    public String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getPermission() {
        return permission;
    }

    public void setPermission(String permission) {
        this.permission = permission;
    }
}

Handler抽象类

创建一个Handler抽象类,这个类维护了链路中的下一个对象,并且将真正处理逻辑的方法doHandler只进行了抽象定义,具体留给实现类去实现:

package com.zwx.design.pattern.chainOfResponsibility;

public abstract class MyHandler {
    protected MyHandler next;

    public void next(MyHandler handler){
        this.next = handler;
    }

    public abstract void doHandler(LoginUser loginUser);
}

链路节点Handler实现类

接下来就是创建具体的实现类来实现MyHandler类。链中的每个节点只处理一件事,所以这个示例中我们可以拆分分三个节点,一个校验账号密码,一个校验角色,一个校验角色。

校验账号密码Handler

创建一个节点用来校验账号密码:

package com.zwx.design.pattern.chainOfResponsibility;

import org.apache.commons.lang3.StringUtils;

public class VerifyAccountHandler extends MyHandler {
    @Override
    public void doHandler(LoginUser loginUser) {
        if (StringUtils.isBlank(loginUser.getLoginName())){
            System.out.println("用户名不能为空");
            return;
        }
        if (StringUtils.isBlank(loginUser.getPassword())){
            System.out.println("密码不能为空");
            return;
        }
        if (!loginUser.getPassword().equals("123456")){
            System.out.println("密码不正确");
            return;
        }
        System.out.println("账号密码校验通过");
        
        next.doHandler(loginUser);
    }
}

注意最后一句话,next.doHandler(loginUser)是用来调用链路中下一个节点的处理方法

校验角色Handler

新增一个校验角色Handler

package com.zwx.design.pattern.chainOfResponsibility;

public class VerifyRoleHanlder extends MyHandler {
    @Override
    public void doHandler(LoginUser loginUser) {
        if(!"admin".equals(loginUser.getRoleName())){
            System.out.println("角色信息有误");
            return;
        }
        System.out.println("角色信息校验通过");
        
        next.doHandler(loginUser);
    }
}

同样的,这里也需要调用下一个节点的处理方法

校验权限Handler

新增一个校验权限的Handler:

package com.zwx.design.pattern.chainOfResponsibility;

public class VerifyPermissionHanlder extends MyHandler {
    @Override
    public void doHandler(LoginUser loginUser) {
        if (!"admin".equals(loginUser.getPermission())){
            System.out.println("暂无权限");
            return;
        }
        System.out.println("权限校验通过,登录成功");
    }
}

因为permission已经是最后一个节点,所以这里不需要再继续制定下一个节点了,内部也没有再维护下一个节点对象了。

测试运行结果

现在让我们来看一下应该如何调用上面的示例:

package com.zwx.design.pattern.chainOfResponsibility;

public class TestChain {
    public static void main(String[] args) {
        MyHandler accountHandler = new VerifyAccountHandler();
        MyHandler roleHanlder = new VerifyRoleHanlder();
        MyHandler permissionHanlder = new VerifyPermissionHanlder();

        accountHandler.next(roleHanlder);
        roleHanlder.next(permissionHanlder);

        LoginUser loginUser = new LoginUser();
        loginUser.setLoginName("孤狼1号");
        loginUser.setPassword("123");
        loginUser.setRoleName("admin");
        loginUser.setPermission("admin");
        accountHandler.doHandler(loginUser);//从起点开始调用
    }
}

输出结果:

密码不正确

如果将密码修改为正确密码123456,则输出如下结果:

账号密码校验通过
角色信息校验通过
权限校验通过,登录成功

和传统写法对比

我们先来看下传统的这种登录逻辑的写法:

package com.zwx.design.pattern.chainOfResponsibility;

import org.apache.commons.lang3.StringUtils;

public class LoginService {

    public void login(LoginUser loginUser){
        //1.校验账号密码
        if (StringUtils.isBlank(loginUser.getLoginName())){
            System.out.println("用户名不能为空");
            return;
        }
        if (StringUtils.isBlank(loginUser.getPassword())){
            System.out.println("密码不能为空");
            return;
        }
        if (!loginUser.getPassword().equals("123456")){
            System.out.println("密码不正确");
            return;
        }
        //2.角色
        if(!"admin".equals(loginUser.getRoleName())){
            System.out.println("角色信息有误");
            return;
        }
        //3.校验权限
        if (!"admin".equals(loginUser.getPermission())){
            System.out.println("暂无权限");
            return;
        }
        System.out.println("校验通过,登录成功");
    }
}

看起来写法上似乎比通过责任链模式写法简单明了,但是一堆业务代码全部堆在一起,而且我们示例中的逻辑校验比较简单,如果逻辑变得很复杂,那么将各种逻辑校验做一个解耦拆分对后期维护是非常有利的。

责任链模式结合建造者模式

上面的示例写法中,最后在调用过程中有点不是很优雅,由此我们联想到了建造者模式的链式写法,接下来让我们结合建造者模式来对其进行改写。

改写Handler抽象类

改写时我们只需要对顶层抽象类进行改写:

package com.zwx.design.pattern.chainOfResponsibility.build;

import com.zwx.design.pattern.chainOfResponsibility.LoginUser;

public abstract class BuildHandler<T> {
    protected BuildHandler next;

    public void next(BuildHandler handler){
        this.next = handler;
    }

    public abstract void doHandler(LoginUser loginUser);

    public static class Builder<T>{
        private BuildHandler<T> head;
        private BuildHandler<T> tail;

        public Builder<T> addHanlder(BuildHandler handler){
            if (null == head){//head==null表示第一次添加到队列
                head = this.tail = handler;
                return this;
            }
            this.tail.next(handler);//原tail节点指向新添加进来的节点
            this.tail = handler;//新添加进来的节点设置为tail节点
            return this;
        }

        public BuildHandler<T> build(){
            return this.head;
        }
    }
}

这个类中,我们通过一个静态内部类Builder来讲链构造成一个队列。

测试运行结果

其他三个类只需要实现改造之后的BuilderHanlder类,其他不需要修改,那么我们来看看现在的测试类又应该如何调用:

package com.zwx.design.pattern.chainOfResponsibility.build;

import com.zwx.design.pattern.chainOfResponsibility.*;

public class TestBuildChain {
    public static void main(String[] args) {
        LoginUser loginUser = new LoginUser();
        loginUser.setLoginName("孤狼1号");
        loginUser.setPassword("123456");
        loginUser.setRoleName("admin");
        loginUser.setPermission("admin");

        BuildHandler.Builder builder = new BuildHandler.Builder();
        builder.addHanlder(new VerifyAccountHandler())
                .addHanlder(new VerifyRoleHanlder())
                .addHanlder(new VerifyPermissionHanlder());
        builder.build().doHandler(loginUser);
    }
}

输出结果:

账号密码校验通过
角色信息校验通过
权限校验通过,登录成功

可以看到,改写之后在调用时会优雅很多。

责任链模式角色

从上面的示例中,可以明确,责任链模式只有两个角色:

  • 1、抽象处理者(Handler)::定义一个请求处理的方法,并维护一个下一个处理节点的Handler对象
  • 2、具体处理者(ConcreteHandler):对请求就行处理,只处理自己部分,处理完之后可以进行转发

责任链模式适用场景

责任链模式主要是解耦了请求与处理,用户只需要将请求发送到链上即可,无需关心请求的具体内容和处理细节,请求会自动进行传递直至有节点进行处理。可以适用于如下场景:

  • 1、多个对象可以处理同一请求,但具体由哪个对象处理则在运行时动态决定。
  • 2、在不明确指定接收者的情况下,向多个对象中的一个提交请求
  • 3、可以动态指定一组对象的处理请求。

责任链模式源码中体现

责任链模式应用比较广泛的就是拦截器。
我们先一下Servlet中的J2EE规范定义的一个拦截器接口:
在这里插入图片描述
我们发现这脸只有一个doFilter方法,并没有维护一个链里面的下一个对象。那么这个是怎么实现链路传递的呢?
我们看一下Spring的实现MockFilterChain:
在这里插入图片描述
在这里插入图片描述
从上面两段代码可以发现,子类通过一个List来构建“链路”,最终调用的时候就是通过遍历List来实现“链路”传递。

责任链模式优缺点

任何一个设计模式都有优点和缺点,那么责任链模式有何优缺点呢?

优点

  • 1、将请求与处理解耦
  • 2、请求处理者(链路中的节点)只需关注自己感兴趣的请求进行处理,对于不感兴趣或者无法处理的请求直接转发给下一个处理者
  • 3、具备链式传递请求的功能,请求发送者无需知晓链路结构,只需等待请求处理结果
  • 4、链路结构灵活,可以通过改变链路结构动态的新增或者删减责任
  • 5、易于扩展新的请求处理类,符合开闭原则

缺点

  • 1、如果责任链的链路太长或者处理时间过程,会影响性能。
  • 2、如果节点对象存在循环引用时,会造成死循环,导致系统崩溃

总结

本文介绍了责任链模式的基本用法,并通过一个示例将责任链模式和建造者模式结合起来使用,使得代码更加优雅,同时也介绍了Spring中对责任链模式的应用,希望通过本文的学习,大家可以更好地理解责任链模式的原理,可以在合适的场景中进行实际应用。
请关注我,和孤狼一起学习进步

  • 22
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
责任链模式建造者模式可以很好地结合在一起,以实现复杂的业务场景。下面是一个简单的示例: 假设有一个流程,需要依次经过多个处理节点,每个节点都有不同的处理逻辑,并且需要按照一定的顺序执行。同时,每个节点的处理结果都需要传递给下一个节点进行处理。这种情况下,我们可以使用责任链模式来实现。 在责任链模式,我们将每个节点看作一个处理器,每个处理器都有一个指向下一个处理器的引用。当请求到达某个处理器时,该处理器会根据自己的逻辑来处理请求,并将处理结果传递给下一个处理器。如果当前处理器无法处理请求,它会将请求传递给下一个处理器。 在这个示例,我们可以使用建造者模式来创建责任。我们可以定义一个构建器(ChainBuilder),它负责创建的每个处理器,并将它们连接起来。具体实现可以参考以下代码: ```java // 定义责任的处理器接口 public interface Handler { void handle(Request request, Handler next); } // 实现责任的处理器 public class HandlerA implements Handler { public void handle(Request request, Handler next) { // 处理请求 // ... // 将处理结果传递给下一个处理器 next.handle(request, next); } } public class HandlerB implements Handler { public void handle(Request request, Handler next) { // 处理请求 // ... // 将处理结果传递给下一个处理器 next.handle(request, next); } } // 定义请求类 public class Request { // 请求参数 } // 定义构建器 public class ChainBuilder { private Handler head; private Handler tail; public ChainBuilder add(Handler handler) { if (head == null) { head = handler; tail = handler; } else { tail.next(handler); tail = handler; } return this; } public Handler build() { return head; } } // 在客户端代码使用责任链模式 ChainBuilder builder = new ChainBuilder(); builder.add(new HandlerA()) .add(new HandlerB()); Handler chain = builder.build(); chain.handle(new Request(), chain); ``` 在上面的代码,我们使用构建器来创建责任。我们可以通过`add`方法向添加处理器,然后使用`build`方法来构建。最后,我们可以使用`handle`方法来处理请求,将请求传递给的第一个处理器。这里需要注意的是,我们需要将的最后一个处理器的`next`指向第一个处理器,以形成一个环形
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

双子孤狼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值