从零开始写拦截器

拦截器的应用是非常广泛的,okhttp中有拦截器,spring框架中也有拦截器,这个年代,你要是不知道什么是拦截器你都不好意思说你是程序员。为了防止大家不好意思出门打招呼,今天我带大家从零开始写一个简单的拦截器。

需求

我们初始需求就是对一个数字乘以10,然后返回结果
这个简单,我们这样处理:

    public int handleNum(int num){
        return num*10;
    }

增加需求1: 如果这个数字是2的倍数,要减去1

public int handleNum(int num){
        if(num % 2 == 0){
            num = num -1;
        }
        return num*10;
    }

增加需求2:在乘10之前,如果数字是99,要减去10

public int handleNum(int num){
        if(num % 2 == 0){
            num = num -1;
        }
        if(num == 99){
            num -= 10;
        }
        return num*10;
    }

随着需求的不断增加,我们就会发现这个handleNum()方法不断的膨胀,而且每一次修改都有可能影响之前的功能,这样的设计是非常不合理的。所以,拦截器这个时候就起到作用了。

基本责任链的拦截器

首先是一个抽象处理类:

public abstract class NumberHandler {
    /**
     * 处理拦截器的方法
     * @param num 待处理的数字
     * @return 处理后的数字
     */
    abstract int handleNum(int num);
}

然后分别是两个需求对应的两个拦截器,首先是为了实现:如果这个数字是2的倍数,要减去1

public class EvenNumHandler extends NumberHandler {
    @Override
    int handleNum(int num) {
       if(num % 2 == 0){
           num -= 1;
       }
        return num;
    }
}

接着是:在乘10之前,如果数字是99,要减去10

public class NinetyNineHnadler extends NumberHandler {
    @Override
    int handleNum(int num) {
        if(num  == 99){
            num -= 10;
        }
        return num;
    }
}

然后是拦截器的调用:

public  int handle(int num){
        List<NumberHandler> handlers = new ArrayList<>();
        handlers.add(new EvenNumHandler());
        handlers.add(new NinetyNineHnadler());
        int res = num;
        for(NumberHandler handler : handlers){
            res = handler.handleNum(res);
        }
        return res * 10;
    }

至此,我们成功实现了一个基本的拦截器,无论对“传入”的数字有什么特殊需求,我们都可以添加一个基本拦截器实现,而且不会影响其他需求,注意,这里核心功能乘以10并不是由拦截器实现的。

增强版拦截器

现在,我们加入一个新的需求:
对乘以10后的数字,如果等于970要减去1
面对这样一个需求,我们发现之前的拦截器有一个缺陷,它只能对输入数据做拦截,确拦截不了返回的数据,也就是乘以10之后的数据,我们看,如果要实现这个需求,该如何改造我们的拦截器:

方案一:

改造后的handler

public abstract class NumberHandler {
    /**
     * 处理拦截器的方法
     * @param num 待处理的数字
     * @return 处理后的数字
     */
    abstract int handleBefore(int num);

    abstract int handleAfter(int num);
}

然后看下之前的EvenNumHandler:

public class EvenNumHandler extends NumberHandler {
    @Override
    int handleBefore(int num) {
       if(num % 2 == 0){
           num -= 1;
       }
        return num;
    }

    @Override
    int handleAfter(int num) {
        return num;
    }
}

看下实现新增需求的handler:

public class NineHundredHandler extends NumberHandler {
    @Override
    int handleBefore(int num) {
        return num;
    }

    @Override
    int handleAfter(int num) {
        if(num == 990){
            num -= 1;
        }
        return num;
    }
}

然后看下调用:

public static int handle(int num){
 
        List<NumberHandler> handlers = new ArrayList<>();
        handlers.add(new EvenNumHandler());
        handlers.add(new NinetyNineHnadler());
        handlers.add(new NineHundredHandler());
        int res = num;
        for(NumberHandler handler : handlers){
            res = handler.handleBefore(res);
        }
        res = res * 10;
        for(NumberHandler handler : handlers){
            res = handler.handleAfter(res);
        }
        return res ;
    }

我们发现这种做法确实实现了请求参数(乘以10之前的数)拦截和返回值的拦截(乘以10之后的数),但这种做法有缺陷:

  • 进行了两次循环
  • 乘以10这个功能无法放置到拦截器中
  • before和after方法很容易遗漏,比如我只关注before方法,不小心就会把After方法返回0

方案二

方案一使用了两个方法来分别拦截入参和返回值,从而带来了一些缺点,那我能不能用一个方法实现呢?
仔细分析下其实是可行的:

public class EvenNumHandler extends NumberHandler {
    @Override
    int handleNum(int num) {
        if (num % 2 == 0) {
            num -= 1;
        }
        //核心在这句
        NumberHandler next = getNextHandler();
        num = next.handleNum(num);
        if(num == 990){
            num -= 1;
        }
        return num;
    }
}

可以看到实现的关键就在于先把数据交给下一个拦截器处理,下一个拦截器返回成功后,我们再处理返回值。
这样写我们要解决两个问题:

  • 在一个拦截器中如何获取另一个拦截器
  • 其他开发人员开发拦截器时如何知道要先调用下一个拦截器的方法
    这两个问题是每一个拦截器开发人员都需要考虑的,因此我们完全可以将其封装起来.首先是如何获取下一个拦截器处理的结果,我们新增了一个接口:
public interface Chain {
    /**
     * 获取下一个拦截器处理的结果
     * @param num 此次拦截器处理的入参
     * @return 下一个拦截器处理完成的结果
     */
    int proceed(int num);

    /**
     * 获取要处理的数字
     * @return 需要处理的数字
     */
    int getNum();
}

然后看下他的默认实现:

public class RealChain implements Chain {
    /**
     * 当前是第几个拦截器
     */
    private int index;
    private List<NumberHandler>numberHandlers;
    /**
     * 待处理的数字
     */
    private int num;


    public RealChain(int index, List<NumberHandler> numberHandlers, int num) {
        this.index = index;
        this.numberHandlers = numberHandlers;
        this.num = num;
    }

    @Override
    public int proceed(int num) {
        if(numberHandlers == null || numberHandlers.size() == 0){
            throw new IllegalArgumentException("未设置任何拦截器");
        }
        if(index  >= numberHandlers.size()){
            //没有更多的拦截器了,直接返回这个num
            return num;
        }
        RealChain nextChain = new RealChain(index+1,numberHandlers,num);
        //由于我们在构造
        NumberHandler handler = numberHandlers.get(index);
        return handler.handleNum(nextChain);

    }

    @Override
    public int getNum() {
        return num;
    }
}

这个Chain之所以能获取下一个拦截器的结果就在于我们在Chain里面保存了所有的拦截器和当前索引,能够通过位置找到下一个拦截器,既然RealChain里面都放了这些了,索性将要处理的number也放进去好了.
然后我们看下此时的拦截器应该怎么写:

public class EvenNumHandler extends NumberHandler {
    @Override
    int handleNum(Chain chain) {
        int num = chain.getNum();
        if (num % 2 == 0) {
            num -= 1;
        }
        //获取下一个拦截器处理的结果
        int responseNum = chain.proceed(num);
        //此处处理返回后的结果
        if(responseNum == 970){
            responseNum -= 1;
        }
        return responseNum;
    }
}

然后我们看下调用:

 public static int handle(int num){
        List<NumberHandler> handlers = new ArrayList<>();
        handlers.add(new EvenNumHandler());
        handlers.add(new NinetyNineHnadler());
        //必须保证最后一个拦截器是实现乘以10的核心拦截器
        handlers.add(new TenTimesHandler());
        RealChain chain = new RealChain(0,handlers,num);
        return chain.proceed(num);
    }

这种调用方式很明显比之前那个两次循环优雅多了,而且乘以10的核心功能也放到了拦截器中:

public class TenTimesHandler extends NumberHandler {
    @Override
    int handleNum(Chain chain) {
        int num = chain.getNum();
        //最后一个拦截器比较特殊,这里实现核心功能,肯定是最后一个,因此无需调用chain.proceed()
        return num *10;
    }
}

这样我们就实现了所有功能都是通过拦截器来操作的f妈妈再也不用担心需求变动影响以前的业务了!!!

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 136,492评论 19 139
  • 在工作中引入Docker用在开发测试环境中,还是获得超强效果。基于两个前提 物理机不够 配置成本高 针对痛点引入银...
    大眼铅笔阅读 315评论 0 0
  • 转载 虽然ViewPager中提供的有setOffscreenPageLimit()来控制其预加载的数目,但是当设...
    蜂鸟之歌阅读 1,761评论 1 50
  • 之所以要聊聊这个话题,是因为此刻女儿坐的车正在塞北草原间穿行,明天凌晨就能回到我出生的地方。同时,有一个云南白族的...
    陈望伟阅读 308评论 0 0
  • Cover letter中文叫做投稿信,是在投稿的时候写给编辑的信,作用跟古时候拜访人家用的拜帖有点相似。如果把期...
    英论阁学术院阅读 11,182评论 0 3

友情链接更多精彩内容