责任链模式——分离职责,动态组合

  责任链模式是将一连串的业务职责进行细分,使多个对象各自负责其中一部分的职责,然后将这些对象连城一条链,将请求沿着这条链进行传递,直到将这个业务逻辑处理完成为止。责任链模式可以用于整条链都是在处理一个功能的场景,比如权限的校验,各自校验一部分,直到链上的节点都走完为止;也可以用在一个功能只需要链上一个节点来处理的场景,在这种场景中,请求在链上传递,直到一个节点处理了请求为止。
  责任链模式的实现思路是写一个抽象的父类,在父类中定义好持有下一个自身节点的引用,然后将真正的业务处理的方法写成抽象方法,让子类去实现,在这个角度来说,责任链和模板方法模式就比较像,但是责任链的重心是后面要将这些具体的实现串在一起,形成一条链条,将请求传给第一个节点,然后一直执行下去。

责任链模式的结构图

责任链模式结构图

  责任链模式的结构图就比较简单,Handler定义了一个持有下一个节点的抽象类,并定义处理业务逻辑的方法,ConcreteHandler类用来实现各自不同的逻辑功能,然后在使用的时候,由客户端Client先将他们穿成一条链,然后再进行使用。

登录权限校验示例

  我们来简单模拟一个登录校验的场景,用于用userId和password两个参数来登录系统,我们先校验其userId是否为空,然后再校验password是否为空,再校验用户名和密码是否匹配,最后校验用户角色是管理员还是普通用户,这些校验中只要有一个失败,就返回登录失败,成功则继续进行下一个校验,直到所有的都完成。代码示例如下:

1. 定义一个顶层父类LoginHandler

public abstract class LoginHandler {

    private LoginHandler nextHandler;

    public void addNext(LoginHandler handler) {
        this.nextHandler = handler;
    }

    public LoginHandler getNextHandler() {
        return this.nextHandler;
    }

    public abstract void authLogin(String userId, String password);

}

2. 实现四个校验的子类

/**
 * 用户ID为空校验
 */
public class UserIdEmptyAuth extends LoginHandler {
    @Override
    public void authLogin(String userId, String password) {
        if (userId == null || "".equals(userId.trim())) {
            System.out.println("userId为空,请重新登录");
            return;
        }
        if (getNextHandler() != null) {
            getNextHandler().authLogin(userId, password);
        }
    }
}
/**
 * 密码为空校验
 */
public class PasswordEmptyAuth extends LoginHandler {
    @Override
    public void authLogin(String userId, String password) {
        if (password == null || "".equals(password.trim())) {
            System.out.println("password 为空,请重新登录");
            return;
        }
        if (getNextHandler() != null) {
            getNextHandler().authLogin(userId, password);
        }
    }
}
/**
 * 用户ID和密码匹配校验
 */
public class UserIdMatchPasswordAuth extends LoginHandler {
    @Override
    public void authLogin(String userId, String password) {
        if (!authUserIdAndPassword(userId, password)) {
            System.out.println("用户名或者密码错误,请重新登录");
            return;
        }
        if (getNextHandler() != null) {
            getNextHandler().authLogin(userId, password);
        }
    }

    private boolean authUserIdAndPassword(String userId, String password) {

        if ("user1".equals(userId) && "aaa".equals(password)) {
            return true;
        }
        if ("user2".equals(userId) && "bbb".equals(password)) {
            return true;
        }
        return false;
    }
}
/**
 * 用户权限校验
 */
public class UserIdAuthorityAuth extends LoginHandler {
    @Override
    public void authLogin(String userId, String password) {
        if ("user1".equals(userId)) {
            System.out.println("管理员用户登录成功!");
        } else {
            System.out.println("普通用户登录成功");
        }
    }

}

3. 写一个测试方法,进行测试

public class Client {

    @Test
    public void testChain() {
        // 创建责任链
        LoginHandler userIdEmptyCheck = new UserIdEmptyAuth();
        LoginHandler passwordEmptyCheck = new PasswordEmptyAuth();
        LoginHandler userIdMatchCheck = new UserIdMatchPasswordAuth();
        LoginHandler userIdAuthCheck = new UserIdAuthorityAuth();
        userIdEmptyCheck.addNext(passwordEmptyCheck);
        passwordEmptyCheck.addNext(userIdMatchCheck);
        userIdMatchCheck.addNext(userIdAuthCheck);
        // 开始测试
        testChain(userIdEmptyCheck);

    }

    private void testChain(LoginHandler chain) {
        System.out.println("-------------------测试用户名为空------------------");
        chain.authLogin(null, null);
        System.out.println("-------------------测试密码为空------------------");
        chain.authLogin("user1", null);
        System.out.println("-------------------测试用户名和密码不匹配------------------");
        chain.authLogin("user1", "bbb");
        System.out.println("-------------------测试管理员------------------");
        chain.authLogin("user1", "aaa");
        System.out.println("-------------------测试普通用户------------------");
        chain.authLogin("user2", "bbb");
    }
}

测试结果如下:

-------------------测试用户名为空------------------
userId为空,请重新登录
-------------------测试密码为空------------------
password 为空,请重新登录
-------------------测试用户名和密码不匹配------------------
用户名或者密码错误,请重新登录
-------------------测试管理员------------------
管理员用户登录成功!
-------------------测试普通用户------------------
普通用户登录成功

4. 结合创建者模式,优化创建责任链过程

上面testChain()我们可以看到,在创建责任链的时候,我们先创建了很多的对象,然后又要一个一个设置到下一个节点,创建过程非常的繁琐,而且容易设置错误,我们可以用创建者模式对其进行优化,关于创建者模式的讲解,我们可以参考博客《建造者模式——创建复杂对象的方法》,其实现思路是:定义一个LoginAuthChainBuilder,利用链表的数据结构,将加入的LoginHandler按照加入顺序自然的组成一条链,然后用build()方法返回其头部节点,代码如下:

/**
 * 登录校验构造器类
 */
public class LoginAuthChainBuilder {
    private LoginHandler head;

    private LoginHandler tail;

    public LoginAuthChainBuilder addNext(LoginHandler nextHandler) {
        if (head == null) {
            // 第一个节点,让head和tail都指向同一个开始的节点
            head = nextHandler;
            tail = nextHandler;
            return this;
        }
        // 不是第一个节点,顺序往下链接
        tail.addNext(nextHandler);
        tail = nextHandler;
        return this;
    }

    public LoginHandler build() {
        return head;
    }

}

在client中新增一个testWithBuilder()方法,测试用构建者模式创建责任链的使用

    @Test
    public void testWithBuilder() {
        // 创建责任链
        LoginAuthChainBuilder builder = new LoginAuthChainBuilder();
        LoginHandler chain = builder.addNext(new UserIdEmptyAuth())
            .addNext(new PasswordEmptyAuth())
            .addNext(new UserIdMatchPasswordAuth())
            .addNext(new UserIdAuthorityAuth())
            .build();
        // 开始测试
        testChain(chain);
    }

其测试结果如下:

-------------------测试用户名为空------------------
userId为空,请重新登录
-------------------测试密码为空------------------
password 为空,请重新登录
-------------------测试用户名和密码不匹配------------------
用户名或者密码错误,请重新登录
-------------------测试管理员------------------
管理员用户登录成功!
-------------------测试普通用户------------------
普通用户登录成功

  我们可以看到,两种方式的测试结果是一样的,但是加入了构建者后,创建责任链的方式明显简洁了很多,我们addNext的顺序,就直接反映了责任链的顺序,不用再费力检查是不是哪个节点设置错了。

5. 示例代码结构图如下:

登录校验示例结构图


后记
  个人总结,欢迎转载、评论、批评指正

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值