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、客戶端在使用的時候,首先使用策略執行上下文這個類的構造函數傳入策略實現類,接着調用策略執行上下文這個類的主要入口函數,就可以執行到算法。通過構造函數傳入不同的策略實現類,就可以更換程序的算法邏輯。
策略設計模式缺點就是客戶端需要知道所有的策略實現類。