設計模式,無論是coder們業余聊天,還是面試時面試官喜歡出的問題中,都會看到它的影子。設計模式,是基於面向對象之上的,應用好設計模式,我們在平時開發,還是架構設計,在系統的架構性,可拓展,可維護性方面的考慮都會有質的提升。當我們會一些基礎語法,邏輯控制之后,就需要考慮我現在寫的代碼,在以后的拓展,維護會有那些瓶頸,如何改善,這些問題考慮清楚后在進行編碼,開發就會事半功倍。范圍說小一點,基於Java , 無論是JDK源碼,還是我們每天都在接觸的各種框架,都使用了各種設計模式。
設計模式(Design pattern)代表了最佳的實踐,通常被有經驗的面向對象的軟件開發人員所采用。設計模式是軟件開發人員在軟件開發過程中面臨的一般問題的解決方案。這些解決方案是眾多軟件開發人員經過相當長的一段時間的試驗和錯誤總結出來的。
本文會先着重寫一下策略模式的應用。后期考慮會寫幾篇博文來記錄學習其他設計模式。
PS: 本人是在邊學編寫,如果有錯誤,望指正。
策略模式(Strategy Pattern)
在策略模式中,一個行為可以在系統運行時動態修改。這種類型設計模式是行為型設計模式,策略模式中,會有代表各種策略的對象和執行策略的context上下文對象。策略對象改變context執行的算法。
舉例:
- 出門旅行,擁有很多出行策略,比如飛機,汽車,火車等;
- JDK AWT中的LayoutManager;
- 支付系統中,有很多支付策略,比如支付寶,微信,銀聯。在支付時需要選擇其中一種支付策略進行支付業務。
簡要介紹
- 用途:定義一系列策略,他們實現自己的算法,使用策略模式實現在運行時動態替換各個策略。
- 解決難點:在有很多類似算法的實現中,使用 if … else 會造成難以維護和拓展。
- 使用場景:在一個系統中有很多類,它們之間唯一的區別只在與行為的不同,而且需要系統需要動態選擇這些行為中的一種,如果不使用適當的設計模式,就會產生多重條件選擇語句。
設計
需求說明:系統需要添加支付功能模塊,且支付渠道提供支付寶,微信,銀聯三種,通過各自支付方式進行支付。
上述的三種支付渠道,當用戶選擇一種后,到達后台進行業務處理,會有三個條件選擇,於是偽代碼如下:
if(支付寶){ 支付寶.pay(); //... }else if(微信){ 微信.pay(); //... }else if(銀聯){ 銀聯.pay(); //... }else{ 未知支付方式... }
感覺沒啥問題。
但是假如項目上線了,公司業務壯大,想要引入第四種支付渠道,比如是一個第三方渠道商,就叫支付寶二號,現在需要拓展時,就需要找到這個項目的源代碼,更改IF語句后重新編譯項目,打包運行。這樣就會多出來一堆的維護時間,而且如果期間改錯代碼,也會浪費很多時間。
那么,如何使用策略模式實現這種運行時動態選擇行為策略,而且后期容易拓展的支付系統呢?
首先設計類圖,如下圖
實現
-
抽象類,既然需要動態更改行為,多態是必須的。
public interface IPayStrategy{ public void pay();//支付 }
-
各個行為對象
微信支付
public class WexinPayStrategy implements IPayStrategy { @Override public void pay() { System.out.println("微信支付."); } }
支付寶支付
public class AliPayStrategy implements IPayStrategy{ @Override public void pay() { System.out.println("支付寶支付."); } }
銀聯支付
public class EBankPayStrategy implements IPayStrategy{ @Override public void pay() { System.out.println("銀聯支付."); } }
負責調度,執行入口的上下文 context
public class PayStrategyContext { private IPayStrategy payStrategy; public PayStrategyContext() { } /** * 執行支付 */ public void excutePay() { if (null == payStrategy) { throw new RuntimeException("支付策略未配置"); } payStrategy.pay(); } public IPayStrategy getPayStrategy() { return payStrategy; } public void setPayStrategy(IPayStrategy payStrategy) { this.payStrategy = payStrategy; } }
-
測試類
public class MainTest { public static void main(String[] args) { //執行上下文 PayStrategyContext payStrategyContext = new PayStrategyContext(); IPayStrategy payStrategy = null; //1.支付寶支付 payStrategy = new AliPayStrategy(); payStrategyContext.setPayStrategy(payStrategy); payStrategyContext.excutePay(); //2.微信支付 payStrategy = new WexinPayStrategy(); payStrategyContext.setPayStrategy(payStrategy); payStrategyContext.excutePay(); //3.銀聯支付 payStrategy = new EBankPayStrategy(); payStrategyContext.setPayStrategy(payStrategy); payStrategyContext.excutePay(); } }
執行結果:
另外
以上案例就是整個策略模式的架構,其實通過觀察測試類可以發現,策略模式在編寫執行邏輯時,只要改動的代碼就是在創建對象這里:
那么,我們可以想到動態創建對象的方法之一:反射。如果這樣,那就可以往數據庫中存儲這些策略的標識,然后新建一個工具類負責根據這些標識動態創建各個策略的不同對象。當然,一般來說,在系統中,這些策略應該時單例存在的,所以應該要把上述例子中的各個策略對象變為單例對象,已實現內存優化。
以下是優化后的版本類圖:
這三種支付行為,其實在系統中可以使用單例,以此節省創建對象時間和內存空間。所以作出以下修改:
//這里就拿微信支付這種支付方式作為例子,其他支付方式的類跟這類似 //特別說明:因我們的策略想要讓系統更加智能,選擇對應策略。所以都需要時單例,並且提供 getInstance() // 方法,以獲取這個單例對象; public class WexinPayStrategy implements IPayStrategy { private static WexinPayStrategy wexinPayStrategy; //單例的基本特點:私有化構造,提供獲取單例的靜態方法 private WexinPayStrategy() { } //給外界提供單例 public static WexinPayStrategy getInstance() { if (null == wexinPayStrategy) { wexinPayStrategy = new WexinPayStrategy(); } return wexinPayStrategy; } //實現方法 @Override public void pay() { System.out.println("微信支付."); } }
以后的支付方式,如需拓展,就添加一個單例類,提供 getInstance()
方法,實現 pay()
支付方法,並且將這個類放在 特定的包路徑
下,這樣,我們就可以將支付方式字符串寫入配置文件或者存儲到數據庫中,再利用工具類給context上下文提供這種支付方式的單例對象, context 利用這個單例對象,調用 pay() 方法,以此實現真正支付。
下面是這個工具類:
public class PayStrategyUtils { public static IPayStrategy getPayStrategy(String payType) { try { //這個路徑需要根據你來定義,屬於一種規則,這樣一來,將所有策略都寫在該包下即可 String path = "com.pattern.demo.strategy.im." + payType.concat("PayStrategy"); //反射,類加載器 Class<?> clazz = Class.forName(path); //調用instance()方法獲取單例對象 IPayStrategy instance = (IPayStrategy) clazz.getDeclaredMethod("getInstance").invoke(null, null); return instance; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Load [" + payType.concat("Strategy") + "] Error :", e); } } }
測試類可以變成:
public class MainTest { public static void main(String[] args) { //執行上下文 PayStrategyContext payStrategyContext = new PayStrategyContext(); IPayStrategy payStrategy = null; //1.1支付寶支付 // payStrategy = new AliPayStrategy(); // payStrategyContext.setPayStrategy(payStrategy); // payStrategyContext.excutePay(); // // //1.2微信支付 // payStrategy = new WexinPayStrategy(); // payStrategyContext.setPayStrategy(payStrategy); // payStrategyContext.excutePay(); // // //1.3銀聯支付 // payStrategy = new EBankPayStrategy(); // payStrategyContext.setPayStrategy(payStrategy); // payStrategyContext.excutePay(); // 1.1支付寶支付 payStrategy = PayStrategyUtils.getPayStrategy("Ali"); payStrategyContext.setPayStrategy(payStrategy); payStrategyContext.excutePay(); // 1.2微信支付 payStrategy = PayStrategyUtils.getPayStrategy("Wexin"); payStrategyContext.setPayStrategy(payStrategy); payStrategyContext.excutePay(); // 1.3銀聯支付 payStrategy = PayStrategyUtils.getPayStrategy("EBank"); payStrategyContext.setPayStrategy(payStrategy); payStrategyContext.excutePay(); } }
到這里就會發現,我們的策略就只需要在業務層傳入一個參數即可,這個參數可以存儲在數據庫,也可以是寫在文件中。這樣如果后期需要添加一種策略,只需要添加一個策略的實現(就是類似上面的微信 WexinPayStrategy,支付寶 AliPayStrategy
這些),再添加一個策略參數到配置文件或數據庫即可(就是 Ali,Wexin,EBank
)。
總結
文章主要介紹策略模式的應用場景,和一些基本架構。我看過一些資料,可能在類圖中並沒有工具類,但是為了簡便,所以加了這一點。所有的設計模式,使用到對的場景就是最好的,在項目中會遇到類似業務,可以考慮使用,以減少后期的時間成本,經歷成本。
文字比較啰嗦,很多也沒闡述清楚,所以提供了這個demo的 Github地址,點擊進入。