从零开始写拦截器

拦截器的应用是非常广泛的,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 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 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 ListnumberHandlers;
    /**
     * 待处理的数字
     */
    private int num;


    public RealChain(int index, List 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 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妈妈再也不用担心需求变动影响以前的业务了!!!

你可能感兴趣的:(从零开始写拦截器)