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

责任链模式分析

  • 设计模式系列总览
  • 前言
  • 什么是责任链模式
  • 写法示例
    • 登录用户信息类
    • Handler抽象类
    • 链路节点Handler实现类
      • 校验账号密码Handler
      • 校验角色Handler
      • 校验权限Handler
    • 测试运行结果
    • 和传统写法对比
  • 责任链模式结合建造者模式
    • 改写Handler抽象类
    • 测试运行结果
  • 责任链模式角色
  • 责任链模式适用场景
  • 责任链模式源码中体现
  • 责任链模式优缺点
    • 优点
    • 缺点
  • 总结

设计模式系列总览

设计模式 飞机票
三大工厂模式 登机入口
策略模式 登机入口
委派模式 登机入口
模板方法模式 登机入口
观察者模式 登机入口
单例模式 登机入口
原型模式 登机入口
代理模式 登机入口
装饰者模式 登机入口
适配器模式 登机入口
建造者模式 登机入口
责任链模式 登机入口
享元模式 登机入口
组合模式 登机入口
门面模式 登机入口
桥接模式 登机入口
中介者模式 登机入口
迭代器模式 登机入口
状态模式 登机入口
解释器模式 登机入口
备忘录模式 登机入口
命令模式 登机入口
访问者模式 登机入口
软件设计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规范定义的一个拦截器接口:
【设计模式系列12】责任链模式原理和示例及其在Spring源码中的运用_第1张图片
我们发现这脸只有一个doFilter方法,并没有维护一个链里面的下一个对象。那么这个是怎么实现链路传递的呢?
我们看一下Spring的实现MockFilterChain:
【设计模式系列12】责任链模式原理和示例及其在Spring源码中的运用_第2张图片
【设计模式系列12】责任链模式原理和示例及其在Spring源码中的运用_第3张图片
从上面两段代码可以发现,子类通过一个List来构建“链路”,最终调用的时候就是通过遍历List来实现“链路”传递。

责任链模式优缺点

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

优点

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

缺点

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

总结

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

你可能感兴趣的:(设计模式,java,chain,责任链,建造者,设计模式)