策略模式
策略的特点就是同一种行为的不同实现方式,同一种行为用代码表述就是接口定义的一个方法。不同的实现方式就是这个接口的多个实现类,分别实现了接口中的方法。
大家可以想,只要是实现了这个接口的类,是不是都得必须实现这个方法,这样就能表达了同一种行为的不同实现方式。
举个例子:ThreadPoolExecutor,这个应该不是很陌生,线程池,下面是线程池的构造方法。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
// 使用了策略模式
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
这里只看ThreadPoolExecutor构造方法的最后一个参数RejectedExecutionHandler即可,这个参数的意思是,线程池执行的线程数超出规定的数量以后,再继续执行更多线程的时候,该怎么处理这些超出来的程。
用个比喻来说就是,用管子往一个池子里注水,池子满了以后,想要继续注水的话,应该怎么办,是继续注水让池子里的水溢出来呢?还是说关了阀门,不再注水?还是说把注水的管子移到一边去,就是把这些水浪费了,不让管子再往池子里注水。这些都是处理多余水的策略。当然,等池子里的水不满的时候,还是要重新注水的。
public interface RejectedExecutionHandler {
// 这个方法就是上面说的 同一种行为
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
这个接口就是上面说的同一种行为。
这四个类都是RejectedExecutionHandler的实现,但是他们分别实现了void rejectedExecution(Runnable r, ThreadPoolExecutor executor)这就是说的不同实现方式。
这种通过接口规定方法的写法有什么好处呢?就是方便扩展,还是线程池的例子,现在是只有四种处理超出线程的方式,如果说有以后有更多的处理方式,只需要创建一个类实现RejectedExecutionHandler这个接口就可以了,不需要过多的修改,线程池内部从来不会关心rejectedExecution这个方法的具体实现是什么。
除了方便扩展以外,策略模式还有个好处,就是能消除多个if-else或是switch的判断,这种类型的代码风格。我想大家都见过或是写过一些包含if-else的代码,比如下面这种,这还是看起来算好的
public void getRejectedExecutionHandler(ThreadPoolExecutor executor, Runnable runnable, String type) {
RejectedExecutionHandler rejectedExecutionHandler = null;
if (TextUtils.equals(type, "Caller")) {
rejectedExecutionHandler = new ThreadPoolExecutor.CallerRunsPolicy();
} else if (TextUtils.equals(type, "Abort")) {
rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy();
} else if (TextUtils.equals(type, "DiscardOldest")) {
rejectedExecutionHandler = new ThreadPoolExecutor.DiscardOldestPolicy();
} else if (TextUtils.equals(type, "Discard")) {
rejectedExecutionHandler = new ThreadPoolExecutor.DiscardPolicy();
}
rejectedExecutionHandler.rejectedExecution(runnable, executor);
}
有的代码的if-else比这个还要多,我曾经见过十几个if-else的,这种结构的代码一般是根据不同的类型返回不同的对象,然后再执行特定的方法。
其实,这段代码就很好的提现了Java语言的特点, 这是不是满足了多态的特征呢,父类引用指向子类对象。(父类引用就是rejectedExecutionHandler,子类对象就是每一个分支创建的具体类呢)子类是可以互相替换rejectedExecutionHandler的引用,而且不会引起代码错误。
但是如果想要扩展的时候,是不是非常不友好,需要继续添加if-else,代码成了一大坨,越堆越多。而且这也不符合『 对修改关闭,对扩展开放 』的原则。
那么,这段代码怎么通过策略模式来修改一下呢?
首先要找出变化的部分,不用多说,就是if-else这部分代码,增加,删除if-else分支语句,这部分改变的比较频繁,通过观察代码,发现分支语句有个特点就是具有一一对应关系,上面的代码可以看出有四个对应关系,分别是
- “Caller” 对应 new ThreadPoolExecutor.CallerRunsPolicy();
- "Abort" 对应 new ThreadPoolExecutor.AbortPolicy();
- "DiscardOldest" 对应 new ThreadPoolExecutor.DiscardOldestPolicy();
- "Discard" 对应 new ThreadPoolExecutor.DiscardPolicy();
既然存在一一对应关系,那么我们可以联想到某些存储数据的结构,哪一种是存在一一对应关系的,想了想,集合中的Map<K, V>可不就是这种可以实现一一对应关系嘛。
那么,现在要做的事就是把if-else这种格式转换为Map存储,怎么转化?就是提前将这种对应关系保存起来,(可以在静态代码块中,也可以在构造方法中初始化,就是尽可能早的初始化这些,别等到用的时候还没有数据,就糟糕了)
private Map<String, RejectedExecutionHandler> handlerMap = new HashMap<>();
public void putRejectedExecutionHandler() {
handlerMap.put("Caller", new ThreadPoolExecutor.CallerRunsPolicy());
handlerMap.put("Abort", new ThreadPoolExecutor.CallerRunsPolicy());
handlerMap.put("DiscardOldest", new ThreadPoolExecutor.CallerRunsPolicy());
handlerMap.put("Discard", new ThreadPoolExecutor.CallerRunsPolicy());
}
那么,if-else的方法就可以修改了
public void getRejectedExecutionHandler(ThreadPoolExecutor executor, Runnable runnable, String type) {
RejectedExecutionHandler rejectedExecutionHandler = handlerMap.get(type);
rejectedExecutionHandler.rejectedExecution(runnable, executor);
}
这样,getRejectedExecutionHandler方法以后大概率是不用再修改了,如果需要添加新的分支,只需要在Map中继续put添加新的对应关系就可以了。
这里面核心的转变我觉得就是将代码结构转变成数据结构,if-else就是一种代码结构,转变成Map数据存储结构。
策略模式是一种行为型的模式,主要作用还是为了解耦,把复杂方法简单化。是对类中方法(函数)的优化方式。
好了,以上就是我对策略模式的理解。
如果,大家觉得的文章对自己有帮助的话,可以关注一下我的公众号,有啥好的不好的话,都可以在公众号里面留言,交个朋友。