设计模式之策略模式

文章以jdk并发包中的一个策略模式实现作为开篇。

使用线程池处理并发任务时,当用户提交任务到线程池,线程池因为线程池已满或者线程池处于SHUTDOWN状态拒接任务的时候,会调用reject函数对任务进行后处理,代码如下:

1
2
3
4
5
6
7
8
9
代码摘自:java.util.concurrent.ThreadPoolExecutor

private volatile RejectedExecutionHandler handler;
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();

final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}

在线程池创建的时候,用户会初始化handler变量,或者使用默认的初始化defaultHandler,即AbortPolicy对象,AbortPolicy就是策略的一种实现,该策略丢弃被拒绝的任务,并抛出RejectedExecutionException异常。

1
2
3
4
5
6
7
8
9
10
代码摘自:java.util.concurrent.ThreadPoolExecutor

public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}

策略接口类:

1
2
3
4
5
代码摘自:java.util.concurrent.RejectedExecutionHandler

public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

所有的后处理策略都要实现该接口,ThreadPoolExecutor持有改接口对象,在初始化ThreadPoolExecutor的时候再指定使用哪种策略,下面我们看一下其他策略源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//该策略直接调用被拒绝任务的Run函数强制执行任务
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
//该策略忽略被拒任务,不做任何处理
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
//该策略丢弃阻塞队列中等待最久的任务(下一个被执行的任务),再次提交被拒任务
public static class implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}

到此我们可以画一个简单的类图表示上述类型之间的关系:
策略模式
可以说这是一个很典型的策略模式类图了。

策略模式

其思想是针对一组算法,将每一种算法都封装到具有共同接口的独立的类中,从而是它们可以相互替换。策略模式的最大特点是使得算法可以在不影响客户端的情况下发生变化,从而改变不同的功能。

下图所示为策略模式的UML图,上文所述的ThreadPoolExecutor就是Context,contextInterface指的就是reject函数。
策略模式111

策略模式的优缺点

  • 优点
    1. 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码转移到父类里面,从而避免重复的代码。
    2. 策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。
    3. 使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
  • 缺点
    1. 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
    2. 策略模式造成很多的策略类,每个具体策略类都会产生一个新类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。

应用场景

  • 多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。
  • 需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。
  • 对客户隐藏具体策略(算法)的实现细节,彼此完全独立。

参考文档:
www.w3sdesign.com/strategy_design_pattern.php