java策略设计模式
1、概述
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们可以相互替换,让算法独立于使用它的客户而独立变化。
其实不要被晦涩难懂的定义所迷惑,策略设计模式实际上就是定义一个接口,只要实现该接口,并对接口的方法进行实现,那么不同的实现类就完成了不同的算法逻辑,而使用该接口的地方,则可以根据需要随意更改实现类,因为它们的接口一样额。
因此策略设计模式有三个角色:
抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口实现。此角色给出所有的具体策略类所需的接口。
具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。
策略上下文(Context)角色:持有一个Strategy的引用,并且在某处调用了算法。
注意:算法定义其实就是接口的方法,算法具体逻辑就是接口实现类中对方法的具体实现。
2、代码示例
首先定义一个算法接口。Strategy接口的run方法就是一种算法的定义。
package com.yefengyu.pattern.strategy; public interface Strategy { String run(); }
其次,编写两个类实现Strategy接口,每个实现类都重写run方法(下面只是简单的返回)。实际上不同实现类的run方法是某种算法(业务领域)的不同实现。
package com.yefengyu.pattern.strategy; public class ConcreteStrategyOne implements Strategy { @Override public String run() {
//此处只是简单的返回
return "ConcreteStrategy One 实现的算法"; } }
package com.yefengyu.pattern.strategy; public class ConcreteStrategyTwo implements Strategy { @Override public String run() { //此处只是简单的返回 return "ConcreteStrategy Two 实现的算法"; } }
接着编写一个策略上下文角色,它持有Strategy接口。下面的StrategyContext类就是策略上下文角色,它有一个Strategy类型的属性,并且通过构造函数传入(也可以是其它方式)。在StrategyContext中有一个execute方法,该方法我们想象它是很复杂的,在其中某一步使用到了策略算法。
package com.yefengyu.pattern.strategy; public class StrategyContext { private Strategy strategy; public StrategyContext(Strategy strategy) { this.strategy = strategy; } public void execute() { //StrategyContext 的 execute 有许多事情要做 System.out.println("------"); //天啊,终于执行到算法这里了 String result = strategy.run(); System.out.println(result); //后续还有好多操作。。。 System.out.println("======"); } }
编写客户端来演示:生成两个不同的Strategy的实现类的对象,分别通过构造函数传入到策略上下文角色中,StrategyContext通过执行execute就调用到具体的算法(run)。
package com.yefengyu.pattern.strategy; public class Client { public static void main(String[] args) { Strategy strategy = new ConcreteStrategyOne(); StrategyContext strategyContext = new StrategyContext(strategy); strategyContext.execute(); strategy = new ConcreteStrategyTwo(); strategyContext = new StrategyContext(strategy); strategyContext.execute(); } }
上面的代码中,StrategyContext通过构造函数传入不同的实现类,即可在其execute方法中调用不同的算法。
3、JDK中的策略设计模式-ThreadPoolExecutor
ThreadPoolExecutor使用方式如下:首先定义一个ThreadPoolExecutor对象,通过调用execute方法来执行,注意execute方法需要传入Runnable。
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadPoolExecutor.CallerRunsPolicy()); executor.execute(new Runnable() { @Override public void run() { System.out.println("hello ThreadPoolExecutor"); } });
找到ThreadPoolExecutor的源码,来看看ThreadPoolExecutor是如何使用策略设计模式的。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.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
构造器的其它内容无需关注。只需要注意构造器的最后一个参数和代码的最后一行,就可以明白ThreadPoolExecutor有一个RejectedExecutionHandler 类型的成员变量 handler,而构造函数则为其赋值。
线程池一般先new一个ThreadPoolExecutor对象,然后调用execute方法执行任务,因此我们看看execute方法的实现。
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); }
上面的代码无需仔细看,注意代码中多次调用了reject方法,该方法源码如下,实际就是调用了ThreadPoolExecutor类中RejectedExecutionHandler类型的成员变量handler的方法。
final void reject(Runnable command) { handler.rejectedExecution(command, this); }
查看下RejectedExecutionHandler发现它是一个接口,只有一个方法。
package java.util.concurrent; public interface RejectedExecutionHandler { void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
有了接口,那么实现类在哪里呢?还是在ThreadPoolExecutor类中,只是使用了静态内部类的形式:
public static class CallerRunsPolicy implements RejectedExecutionHandler { public CallerRunsPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } } 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()); } } public static class DiscardPolicy implements RejectedExecutionHandler { public DiscardPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } } public static class DiscardOldestPolicy implements RejectedExecutionHandler { public DiscardOldestPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } } }
经过上面的一系列源码探究,我终于要使出对比大法了。
(a): RejectedExecutionHandler接口中的rejectedExecution就是策略模式中的算法,此处算法实际叫做拒绝策略:如果线程数量大于等于 maximumPoolSize,且 workQueue 已满,则使用拒绝策略处理新任务。
(b): CallerRunsPolicy 、AbortPolicy 、AbortPolicy 、DiscardOldestPolicy 是拒绝策略算法的具体实现。和上面的演示示例相比,此处直接在策略上下文环境直接使用静态内部类的方式实现了抽象的策略,也就是接口中算法的定义。
(c): ThreadPoolExecutor就是策略的上下文(Context)角色。它持有一个RejectedExecutionHandler的引用,使用构造函数对其赋值,其中有方法execute作为环境上下文的主要入口,并且调用了算法。
(d): 客户端:见本节最上面的代码,在new ThreadPoolExecutor对象的时候可以传入拒绝策略,在使用ThreadPoolExecutor时可以通过传入不同的拒绝策略来到达不同的效果,正是策略模式期望的效果。
4、JDK中的策略设计模式-Thread与Runnable
特别注意:在https://www.cnblogs.com/yefengyu/p/10520531.html文章中,我讲解了Thread是模板设计模式的一种实现,那里前提是使用Thread方式创建线程,而不是使用Runnable方式。
通过上面线程池的对比试验之后,这次分析换一种思路,我们现在把模板设计模式往Thread和Runnable身上套。
(a): 策略算法、也就是抽象策略角色:Runnable接口及其方法run方法。
@FunctionalInterface public interface Runnable { public abstract void run(); }
(b): 算法具体实现
一般由程序员创建一个类实现Runnable接口,重写run方法。
package com.yefengyu.pattern.strategy; public class MyRunnable implements Runnable { @Override public void run() { System.out.println("hello Runnable "); } }
(c): 策略上下文
Thread类持有Runnable接口,并且通过构造函数传入。start方法中隐式调用了Thread类的run方法。
持有抽象策略接口
private Runnable target;
通过构造函数为抽象策略接口赋值
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
策略上下文一般都有一个主要的方法入口,在方法的某一步调用了算法。
public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { } } } private native void start0();
start方法是主要的方法入口,它调用了start0方法,而start0是本地方法,最后还是调用了Thread的run方法(细节自行分析),而Thread的run方法调用了Runnable的run方法。也就是最终调用到了算法。
@Override public void run() { if (target != null) { target.run(); } }
(d): 客户端使用
public Thread(Runnable target) 使用构造方法传入,调用start方法启动线程。
Thread thread = new Thread(new MyRunnable()); thread.start();
通过上面的分析,我们平常使用的线程也使用了策略设计模式,只是这里的策略具体实现交由程序员实现,JDK为我们提供了策略接口、策略上下文等。
5、总结
策略设计模式一般实现步骤如下:
a、编写策略接口
b、编写策略实现类
c、编写策略执行上下文,一般这个类持有一个策略接口属性,通过构造函数为其赋值,并且该类有一个主要的入口函数,该函数有很多个操作步骤,其中某一个步骤要使用算法(也就是接口方法)。
d、客户端在使用的时候,首先使用策略执行上下文这个类的构造函数传入策略实现类,接着调用策略执行上下文这个类的主要入口函数,就可以执行到算法。通过构造函数传入不同的策略实现类,就可以更换程序的算法逻辑。
策略设计模式缺点就是客户端需要知道所有的策略实现类。