责任链模式

2020-02-03 23:50
1183
0
      责任链模式(Chain of Responsibility Pattern),顾名思义包含了链表的作用。因此要理解责任链,觉得在于两点:1、生成链的节点。2、如何链。
      责任链的相关介绍就不多费口舌,可以点击查看相关引用
 
一、关于链的节点生成
      首先要创建一个抽象类。为啥呢?因为要做链式结构,而怎么与下一个节点关联就可以写到这个抽象父类的方法里,然后可以定义一个抽象方法给实现类,来实现具体的业务逻辑。说的好抽象,具体怎样弄还是直接上代码吧。
 
      以打游戏为例,打过第一关才能进入第二关……第N关。
      如下,先建立一个游戏关卡的抽象类,下一关卡作为抽象类的成员变量。当然也可以参考JavaWeb的过滤器,链的下一节点直接在方法中传入。(外链参考:Filter、FilterChain、FilterConfig 介绍
/**
 * 游戏关卡
 */
public abstract class GameLevelHandler {

    /**
     * 下一个关卡
     */
    protected GameLevelHandler gameLevelHandler;
    /**
     * 玩游戏
     */
    public abstract void play();
    /**
     * 玩下一关
     */
    protected void nextLevel() {
        if (gameLevelHandler != null)
            gameLevelHandler.play();// 玩下一关
    }
    public void setGameLevelHandler(GameLevelHandler gameLevelHandler) {
        this.gameLevelHandler = gameLevelHandler;
    }
}
      实现类做如下处理,业务逻辑完成后调用下一关的方法。
/**
 * 第一关
 */
public class FirstLevelHandler extends GameLevelHandler {
    @Override
    public void play() {
        System.out.println("第一关 通关.......");
        nextLevel();//去下一关
    }
}

      到此节点的生成也就差不多了。然后怎么形成链呢?咱继续……

二、如何去链

      到此想一想也能明白了,最简单的方法不就是把每一关的对象都创建起来,然后把每关对应的下一关设置进去。
      稍微优雅点,可以弄个工厂模式写好关联方案,只要玩第一关,后面的自然也就调用下去了。但感觉还是不够优雅啊,代码依然写的很死,如果以后关卡要换呢,那还得改代码。
      可以把这个关联放入数据库中,做一个关联表,有当前关卡id,上一个关卡id,下一关关卡id这几个字段,那么上一个关卡id为空就代表第一关,下一关卡id为空就代表最后一关。这样就用数据库表模拟了一个链表的关联映射。
      然后id的值用什么可以和关卡对象关联呢?如果项目中有spring那当然用spring的bean id即可。如果直接JavaSE写,那可以存储类路径,通过反射得到具体对象。
 
然后……,好抽象,还是直接上菜吧,这里就直接以数据库作链式结构来说。
如下建一个游戏关卡关联表,集成spring,关卡ID就是节点实现类的beanId。
CREATE TABLE `game_level` (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `level_name` varchar(32) DEFAULT NULL COMMENT '关卡名称',
  `level_id` varchar(32) DEFAULT NULL COMMENT '关卡主键id',
  `prev_level_id` varchar(32) DEFAULT NULL COMMENT '上一个关卡',
  `next_level_id` varchar(32) DEFAULT NULL COMMENT '下一个关卡',
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 COMMENT='游戏关卡关联表';

INSERT INTO `game_level` VALUES ('10', '打蚊子', 'firstLevelHandler', NULL, 'secondLevelHandler');
INSERT INTO `game_level` VALUES ('11', '打酱油', 'secondLevelHandler', 'firstLevelHandler', 'thirdLevelHandler');
INSERT INTO `game_level` VALUES ('12', '打怪兽', 'thirdLevelHandler', 'secondLevelHandler', NULL);

然后怎么取,怎么关联呢?继续看代码

    /**
     * 按数据库关联好链条,然后返回第一关
     *
     * @return
     */
    public GameLevelHandler getFirstLevelHandler() {
        // 1.从数据库中查询出第一关
        GameLevelEntity firstEntity = gameLevelMapper.getFirstLevelHandler();
        // 2.获取第一关的beanId,以此获得第一关对象
        String levelId = firstEntity.getLevelId();
        GameLevelHandler firstLevel = SpringUtils.getBean(levelId, GameLevelHandler.class);
        // 3.下一关的ID
        String nextId = firstEntity.getNextLevelId();
        // 4. 设置一个关卡对象标识循环时的当前关卡,类似一个指针初始指向第一关卡
        GameLevelHandler tempLevel = firstLevel;
        while (!StringUtils.isEmpty(nextId)) {//如果下一关ID不为空则一直取
            // 5.获得下一关卡设置到当前关卡中
            GameLevelHandler nextLevel = SpringUtils.getBean(nextId, GameLevelHandler.class);
            tempLevel.setNextLevelHandler(nextLevel);
            // 6.从数据库取出下一关卡的关联信息,看是否还有再下一关
            GameLevelEntity nextEntity = gameLevelMapper.getLevelByID(nextId);
            if (nextEntity == null) {//如果没有再下一关循环结束
                break;
            }
            // 7、如果还有再下一关,赋值给缓存对象继续循环
            nextId = nextEntity.getNextLevelId();
            tempLevel = nextLevel;
        }
        return firstLevel;
    }

      怎么取数据库的代码,在此担心篇幅太长就不粘贴代码了,因为示例的数据库表很简单,通过判断上一关卡的ID为空就能找到第一关。核心的代码逻辑基本如上,扩展的话还可以加上缓存,修改逻辑减少查询数据库次数以增加性能。

三、优缺点

      优点:
             可以动态组合职责
             灵活修改链中的对象
             让请求者和接受者解耦
             各个组件之间也完全解耦。
      缺点:
            功能一旦增多会产生很多细粒度对象
            性能有一定影响
            请求不一定能处理请求,最好提供默认处理,构造链的有效性

四、应用场景

1.多条件流程判断 权限控制
2.OA系统流程审批
3.Java过滤器的底层实现Filter,在Java过滤器中客户端发送请求到服务器端,过滤会经过参数过滤、session过滤、表单过滤、隐藏过滤、检测请求头过滤

全部评论