前言
最近段時間,接到一個需求:開發(fā)一個聚合支付服務,對其他內(nèi)部項目提供統(tǒng)一的接口來實現(xiàn)不同支付平臺的支付能力發(fā)起,比如支付寶,微信,銀聯(lián)等。為了處理相似的支付操作而各平臺具體實現(xiàn)不同的情況,要讓各個平臺接口能力能相互獨立,并要方便擴展后續(xù)新增的支付平臺,我引入了設計模式的策略模式來應對需求場景,借此深入學習總結下策略模式,于是也就有了本文,希望對學習策略模式的同學有所幫助。
為什么需要策略模式
日常工作開發(fā)中我們總會遇到如下熟悉的代碼片段:
if(condition1){
// do something1
} else if (condition2){
// do something2
} else if (condition3){
// do something3
}
在每個 if 條件下都有數(shù)十行甚至百行的業(yè)務處理,各自處理又是相互獨立的并且目的一致,都匯聚在一個方法里。這樣的寫法不但讓類變得臃腫冗長,并且不同邏輯都在一個類中修改,維護和擴展起來都很費勁。那么又有什么辦法可以優(yōu)化這大段的代碼呢,在實現(xiàn)功能的同時,讓代碼更加靈活和易維護。
要解決這個問題,本文的主角—策略模式 就登場了,作為設計模式中比較簡單的行為型模式,其實很多框架中都見到它的身影,稍后我們也會從各框架源碼中識別策略模式的應用。使用策略模式可以幫助我們將每個處理邏輯封裝成獨立的類,客戶端類需要進行哪種處理邏輯就使用對應的類,調用其封裝了業(yè)務處理細節(jié)的方法即可。這樣一來,客戶端類減少了業(yè)務處理邏輯的大量代碼,讓自身更加精簡。當業(yè)務邏輯有所改動時,只要在對應的類中修改,而不影響其他的類;并且如果出現(xiàn)了新的業(yè)務邏輯只要新增相似的類進行實現(xiàn),供客戶端類調用即可。
什么是策略模式
接下來我們就介紹下策略模式的定義和組成,以及它的基本形式。
首先看下維基百科上策略模式的定義:
In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use.
策略模式也叫政策模式,允許程序在運行時選擇一個算法執(zhí)行,通常存在一類算法實現(xiàn)提供外部選擇執(zhí)行,這里的算法,也可以叫做策略,相當于上節(jié)內(nèi)容提到的具體處理邏輯。
再來看下 《設計模式:可復用面向對象軟件的基礎》一書中對策略模式的定義:
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from the clients that use it.
再次對其定義解讀:定義一類算法,各自獨立封裝實現(xiàn),并且相互之間是可替換的。除此之外,由客戶端類決定具體使用哪個算法。
上述兩個定義都提到了算法一詞,它表示了完整的,不可再拆分的業(yè)務邏輯處理。通常用接口或者抽象類來表示一類算法的抽象,提供多種對該類算法的操作實現(xiàn),以此組成一類獨立且可替換的算法,也叫策略組。
了解完定義后,我們再來看下策略模式通用類圖:

類圖中涉及三類角色:Context,Strategy 和 ConcreteStrategy
- Strategy:抽象策略角色,代表某個算法的接口或者抽象類,定義了每個算法或者策略需要具有的方法和屬性。
- Context:上下文角色,引用策略接口對象,屏蔽了外部模塊對策略或方法的直接訪問,只能通過Context 提供的方法訪問。
- ConcreteStrategy:抽象策略的具體實現(xiàn),該類含有具體的算法,并且通常不只一種實現(xiàn),有多個類。
這三個角色的功能職責都十分明確,對應的源碼實現(xiàn)也十分簡單,現(xiàn)在我們就來快速看下每個角色對應的通用源碼。
// 抽象的策略角色
public interface Strategy {
void doSomething();
}
// 具體策略角色
public class ConcreteStrategy implements Strategy {
@Override
public void doSomething() {
System.out.println("ConcreteStrategy doSomething !");
}
}
// 上下文角色
public class Context {
private final Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void doAnything() {
this.strategy.doSomething();
}
}
有了策略模式的基本代碼結構,在客戶端類中使用十分簡單,想要哪個策略,就產(chǎn)生出它的具體策略對象放入上下文對象內(nèi),然后由上下文對象執(zhí)行具體策略操作即可,具體代碼如下:
public class Client {
public static void main(String[] args) {
Strategy strategy = new ConcreteStrategy();
Context context = new Context(strategy);
context.doAnything(); // ConcreteStrategy doSomething !
}
}
識別策略模式
看清楚了策略模式的定義,角色組成以及通用的代碼結構之后,我們就來看下策略模式在通用框架里的應用,來加深對策略模式的認識。
JDK 與策略模式
在常用的Java 集合框架中,比較器 java.util.Comparator 的設計就采用了策略模式。Comparator 就是一個抽象的策略接口,只要一個類實現(xiàn)這個接口,自定 compare 方法,該類成為具體策略類,你可以在很多地址找到這個抽象策略接口的實現(xiàn),官方在工具類 java.util.Comparators 里也提供 NaturalOrderComparator,NullComparator 兩種具體策略類。而使用 Comparator 到的 java.util.Collections 類就是 Context 角色,將集合的比較功能封裝成靜態(tài)方法對外提供。
Spring Framework 與策略模式
Spring 框架最早以 IoC 和 DI 兩大特性著稱,不需要開發(fā)者自己創(chuàng)建對象,而是通過 Spring IoC 容器識別然后實例化所需對象。在 Spring 中將執(zhí)行創(chuàng)建對象實例的這個操作封裝為一種算法,用接口類 org.springframework.beans.factory.support.InstantiationStrategy 進行聲明,而具體策略類則有 org.springframework.beans.factory.support.SimpleInstantiationStrategy 和 org.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy 兩個,并且 CglibSubclassingInstantiationStrategy 是對 SimpleInstantiationStrategy 的繼承擴展,也是 Spring 容器中真正使用到的策略類,具體應用的源碼可參考 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory 類:
/**
* Instantiate the given bean using its default constructor.
* @param beanName the name of the bean
* @param mbd the bean definition for the bean
* @return a BeanWrapper for the new instance
*/
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
//...
beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
//...
}
如何使用策略模式
實例應用
俗話說學以致用,接觸了策略模式后我們應該想想怎么用在自己日常開發(fā)項目中呢,這里就簡單通過一個實例來說明下策略模式的使用方式。假設現(xiàn)在有個需求:需要對一個目錄或者文件實現(xiàn)兩種不同格式的解壓縮方式:zip壓縮和gzip壓縮,也后續(xù)可能新增其他的解壓縮方式。
我們首先將解壓縮的算法抽象成抽象策略接口 CompressStrategy, 提供壓縮方法 compress 和解壓縮方法 uncompress,分別接受源文件路徑和目的文件路徑。
策略類在命名通常上以 Strategy 為后綴,來指明自身采用策略模式進行設計,以此簡化與其他人溝通成本。
public interface CompressStrategy {
public boolean compress(String source, String to);
public boolean uncompress(String source, String to);
}
再對抽象策略接口進行實現(xiàn),分別提供zip 壓縮算法和 gzip 壓縮算法,代碼如下:
public class ZipStrategy implements CompressStrategy {
@Override
public boolean compress(String source, String to) {
System.out.println(source + " --> " + to + " ZIP壓縮成功!");
return true;
}
@Override
public boolean uncompress(String source, String to) {
System.out.println(source + " --> " + to + " ZIP解壓縮成功!");
return true;
}
}
public class GzipStrategy implements CompressStrategy {
@Override
public boolean compress(String source, String to) {
System.out.println(source + " --> " + to + " GZIP壓縮成功!");
return true;
}
@Override
public boolean uncompress(String source, String to) {
System.out.println(source + " --> " + to + " GZIP解壓縮成功!");
return true;
}
}
代碼示例里的實現(xiàn)為了簡化只是簡單打印操作,具體實現(xiàn)可以參考 JDK API 進行操作。
接下來看下 Context 角色的代碼實現(xiàn):
public class CompressContext {
private CompressStrategy compressStrategy;
public CompressContext(CompressStrategy compressStrategy) {
this.compressStrategy = compressStrategy;
}
public boolean compress(String source, String to) {
return compressStrategy.compress(source, to);
}
public boolean uncompress(String source, String to) {
return compressStrategy.uncompress(source, to);
}
}
十分簡單,只是傳入一個具體算法,然后執(zhí)行,到這里標準的策略模式就編寫完畢了??蛻舳祟愔皇歉鶕?jù)需要指定的具體壓縮策略對象傳給 CompressContext 對象即可。如果要新增一個壓縮算法,也只需對 CompressStrategy 接口提供新的實現(xiàn)即可傳給 CompressContext 對象使用。
public class Client {
public static void main(String[] args) {
CompressContext context;
System.out.println("========執(zhí)行算法========");
context = new CompressContext(new ZipStrategy());
context.compress("c:\\file", "d:\\file.zip");
context.uncompress("c:\\file.zip", "d:\\file");
System.out.println("========切換算法========");
context = new CompressContext(new GzipStrategy());
context.compress("c:\\file", "d:\\file.gzip");
context.uncompress("c:\\file.gzip", "d:\\file");
}
}
上面的策略模式的應用示例是不是很簡單,類似應用也有很多,比如要對接第三方支付,不同的支付平臺有不同的支付API,這個API操作都可以抽象成策略接口,客戶端發(fā)起特定平臺的支付接口時,我們只需調用具體的支付策略類執(zhí)行,并且每個支付策略類相互獨立,可替換。
適用場景
本節(jié)最后簡單總結下策略模式的適用場景:
如果一個對象有很多的行為,它們的實現(xiàn)目的相同,而這些行為使用了多重的條件選擇語句來實現(xiàn)。
當一個系統(tǒng)需要動態(tài)地切換算法,會選擇一種算法去執(zhí)行。
客戶端類不需要知道具體算法的實現(xiàn)細節(jié),只要調用并完成所需要求。
Lambda 與 策略模式
JDK 8 之后,利用Lambda可以提供策略模式更加精簡的實現(xiàn),如果策略接口是一個函數(shù)接口,那么不需要聲明新的類來實現(xiàn)不同策略,直接通過傳遞Lambda就可實現(xiàn),并且更加簡潔,具體使用方式參見下方代碼:
/**
* Context 對象
*/
public class Validator {
private final ValidationStrategy strategy;
public Validator(ValidationStrategy v) {
this.strategy = v;
}
public boolean validate(String s) {
return strategy.execute(s);
}
}
/**
* 策略接口
*/
@FunctionalInterface
public interface ValidationStrategy {
boolean execute(String s);
}
numericValidator = new Validator((String s) -> s.matches("[a-z]+"));
b1 = numericValidator.validate("aaaa"); // true
lowerCaseValidator = new Validator((String s) -> s.matches("\\d+"));
b2 = lowerCaseValidator.validate("bbbb"); // false
結合 Lambda 的策略模式更適合用于處理簡單算法操作的場景,如果算法實現(xiàn)復雜過于冗長復雜,還是建議拆分成單個類進行實現(xiàn)。
策略模式的注意點
策略模式使用起來雖然簡單,但它的靈活性在許多項目都能見到其身影,在使用時也有需要注意的地方,下面我們就來看下:
策略模式中每個算法都是完整,不可拆分的原子業(yè)務,并且多個算法必須是可以相互替換,,而用哪個算法由外部調用者決定。
當如果具體策略類超過4個,需要使用混合模式減少類膨脹和對外暴露的問題,通過其他模式修正:工廠方法模式,代理模式,享元模式
策略模式的優(yōu)缺點
一個設計模式的引入必存在它合理的地方和不足,最后我們再說說下策略模式的優(yōu)缺點。
優(yōu)點
- 使用策略模式,可以在不修改原有系統(tǒng)的基礎上更換算法或行為,可以靈活地增加新的算法或行為,提供了系統(tǒng)的擴展性
- 策略模式提供了對一類算法進行管理維護。
- 使用策略模式可以避免使用多重條件判斷,由外部模塊決定所要執(zhí)行的策略類。
缺點
- 客戶端必須知道所有的策略類,并自行決定使用哪一個策略類。
- 會產(chǎn)生很多策略類,使得類的項目增多。
結語
到這里,本文對策略模式的學習就此結束,當然關于策略模式的內(nèi)容遠不止這些,配合其他模式還有用法,感興趣了的同學可以參考文末提供的資料鏈接進一步深入學習。也歡迎掃碼關注微信公眾號:「聞人的技術博客」,定期分享Java技術干貨,共同進步。

推薦閱讀
- 一文掌握 Spring Boot Profiles
- 如何優(yōu)雅關閉 Spring Boot 應用
- 需要接口管理的你了解一下?
- Java 之 Lombok 必知必會
- Java 微服務新生代之 Nacos
參考
- https://en.wikipedia.org/wiki/Strategy_pattern
- https://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/strategy.html#id3
- 設計模式之禪#第18章 策略模式
- Java 8 實戰(zhàn)#8.2 使用 Lambda 重構面向對象的設計模式
- Java Design Patterns #15. Strategy (Policy) Pattern
本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布!