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

前言

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

什么是责任链模式

责任链模式(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中对责任链模式的应用,希望通过本文的学习,大家可以更好地理解责任链模式的原理,可以在合适的场景中进行实际应用。
请关注我,和孤狼一起学习进步

猜你喜欢

转载自blog.csdn.net/zwx900102/article/details/108453667