深刻解析策略模式

  在講策略模式以前,咱們先看一個平常生活中的小例子:java

  現實生活中咱們到商場買東西的時候,賣場每每根據不一樣的客戶制定不一樣的報價策略,好比針對新客戶不打折扣,針對老客戶打9折,針對VIP客戶打8折...算法

  如今咱們要作一個報價管理的模塊,簡要點就是要針對不一樣的客戶,提供不一樣的折扣報價。編程

若是是有你來作,你會怎麼作?多線程

咱們頗有可能寫出下面的代碼:less

package strategy.examp02; import java.math.BigDecimal; public class QuoteManager { public BigDecimal quote(BigDecimal originalPrice,String customType){ if ("新客戶".equals(customType)) { System.out.println("抱歉!新客戶沒有折扣!"); return originalPrice; }else if ("老客戶".equals(customType)) { System.out.println("恭喜你!老客戶打9折!"); originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP); return originalPrice; }else if("VIP客戶".equals(customType)){ System.out.println("恭喜你!VIP客戶打8折!"); originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP); return originalPrice; } //其餘人員都是原價 return originalPrice; } }

通過測試,上面的代碼工做的很好,但是上面的代碼是有問題的。上面存在的問題:把不一樣客戶的報價的算法都放在了同一個方法裏面,使得該方法非常龐大(如今是隻是一個演示,因此看起來還不是很臃腫)。ide

下面看一下上面的改進,咱們把不一樣客戶的報價的算法都單獨做爲一個方法函數

 

 1 package strategy.examp02;  2  3 import java.math.BigDecimal;  4  5 public class QuoteManagerImprove {  6  7 public BigDecimal quote(BigDecimal originalPrice, String customType){  8 if ("新客戶".equals(customType)) {  9 return this.quoteNewCustomer(originalPrice); 10 }else if ("老客戶".equals(customType)) { 11 return this.quoteOldCustomer(originalPrice); 12 }else if("VIP客戶".equals(customType)){ 13 return this.quoteVIPCustomer(originalPrice); 14  } 15 //其餘人員都是原價 16 return originalPrice; 17  } 18 19 /** 20  * 對VIP客戶的報價算法 21  * @param originalPrice 原價 22  * @return 折後價 23 */ 24 private BigDecimal quoteVIPCustomer(BigDecimal originalPrice) { 25 System.out.println("恭喜!VIP客戶打8折"); 26 originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP); 27 return originalPrice; 28  } 29 30 /** 31  * 對老客戶的報價算法 32  * @param originalPrice 原價 33  * @return 折後價 34 */ 35 private BigDecimal quoteOldCustomer(BigDecimal originalPrice) { 36 System.out.println("恭喜!老客戶打9折"); 37 originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP); 38 return originalPrice; 39  } 40 41 /** 42  * 對新客戶的報價算法 43  * @param originalPrice 原價 44  * @return 折後價 45 */ 46 private BigDecimal quoteNewCustomer(BigDecimal originalPrice) { 47 System.out.println("抱歉!新客戶沒有折扣!"); 48 return originalPrice; 49  } 50 51 }

 

上面的代碼比剛開始的時候要好一點,它把每一個具體的算法都單獨抽出來做爲一個方法,當某一個具體的算法有了變更的時候,只須要修改響應的報價算法就能夠了。測試

可是改進後的代碼仍是有問題的,那有什麼問題呢?this

1.當咱們新增一個客戶類型的時候,首先要添加一個該種客戶類型的報價算法方法,而後再quote方法中再加一個else if的分支,是否是感受非常麻煩呢?並且這也違反了設計原則之一的開閉原則(open-closed-principle).lua

開閉原則:

  對於擴展是開放的(Open for extension)。這意味着模塊的行爲是能夠擴展的。當應用的需求改變時,咱們能夠對模塊進行擴展,使其具備知足那些改變的新行爲。也就是說,咱們能夠改變模塊的功能。

  對於修改是關閉的(Closed for modification)。對模塊行爲進行擴展時,沒必要改動模塊的源代碼或者二進制代碼。

2.咱們常常會面臨這樣的狀況,不一樣的時期使用不一樣的報價規則,好比在各個節假日舉行的各類促銷活動時、商場店慶時每每都有廣泛的折扣,可是促銷時間一旦過去,報價就要回到正常價格上來。按照上面的代碼咱們就得修改if else裏面的代  碼非常麻煩

那有沒有什麼辦法使得咱們的報價管理便可擴展、可維護,又能夠方便的響應變化呢?固然有解決方案啦,就是咱們下面要講的策略模式。

 

定義:

  策略模式定義了一系列的算法,並將每個算法封裝起來,使每一個算法能夠相互替代,使算法自己和使用算法的客戶端分割開來,相互獨立。

結構:

  1.策略接口角色IStrategy:用來約束一系列具體的策略算法,策略上下文角色ConcreteStrategy使用此策略接口來調用具體的策略所實現的算法。

  2.具體策略實現角色ConcreteStrategy:具體的策略實現,即具體的算法實現。

  3.策略上下文角色StrategyContext:策略上下文,負責和具體的策略實現交互,一般策略上下文對象會持有一個真正的策略實現對象,策略上下文還可讓具體的策略實現從其中獲取相關數據,回調策略上下文對象的方法。

UML類圖:

                                                   

UML序列圖:

                                               

策略模式代碼的通常通用實現

策略接口

1 package strategy.examp01; 2 3 //策略接口 4 public interface IStrategy { 5 //定義的抽象算法方法 來約束具體的算法實現方法 6 public void algorithmMethod(); 7 }

具體的策略實現:

 1 package strategy.examp01;  2  3 // 具體的策略實現  4 public class ConcreteStrategy implements IStrategy {  5 //具體的算法實現  6  @Override  7 public void algorithmMethod() {  8 System.out.println("this is ConcreteStrategy method...");  9  } 10 }
 1 package strategy.examp01;  2  3 // 具體的策略實現2  4 public class ConcreteStrategy2 implements IStrategy {  5 //具體的算法實現  6  @Override  7 public void algorithmMethod() {  8 System.out.println("this is ConcreteStrategy2 method...");  9  } 10 }

策略上下文:

 1 package strategy.examp01;  2  3 /**  4  * 策略上下文  5 */  6 public class StrategyContext {  7 //持有一個策略實現的引用  8 private IStrategy strategy;  9 //使用構造器注入具體的策略類 10 public StrategyContext(IStrategy strategy) { 11 this.strategy = strategy; 12  } 13 14 public void contextMethod(){ 15 //調用策略實現的方法 16  strategy.algorithmMethod(); 17  } 18 }

外部客戶端:

 1 package strategy.examp01;  2  3 //外部客戶端  4 public class Client {  5 public static void main(String[] args) {  6 //1.建立具體測策略實現  7 IStrategy strategy = new ConcreteStrategy2();  8 //2.在建立策略上下文的同時,將具體的策略實現對象注入到策略上下文當中  9 StrategyContext ctx = new StrategyContext(strategy); 10 //3.調用上下文對象的方法來完成對具體策略實現的回調 11  ctx.contextMethod(); 12  } 13 }

 

針對咱們一開始講的報價管理的例子:咱們能夠應用策略模式對其進行改造,不一樣類型的客戶有不一樣的折扣,咱們能夠將不一樣類型的客戶的報價規則都封裝爲一個獨立的算法,而後抽象出這些報價算法的公共接口

公共報價策略接口:

1 package strategy.examp02;
2 
3 import java.math.BigDecimal;
4 //報價策略接口
5 public interface IQuoteStrategy {
6     //獲取折後價的價格
7     BigDecimal getPrice(BigDecimal originalPrice);
8 }

新客戶報價策略實現:

 1 package strategy.examp02;
 2 
 3 import java.math.BigDecimal;
 4 //新客戶的報價策略實現類
 5 public class NewCustomerQuoteStrategy implements IQuoteStrategy {
 6     @Override
 7     public BigDecimal getPrice(BigDecimal originalPrice) {
 8         System.out.println("抱歉!新客戶沒有折扣!");
 9         return originalPrice;
10     }
11 }

老客戶報價策略實現:

 1 package strategy.examp02;
 2 
 3 import java.math.BigDecimal;
 4 //老客戶的報價策略實現
 5 public class OldCustomerQuoteStrategy implements IQuoteStrategy {
 6     @Override
 7     public BigDecimal getPrice(BigDecimal originalPrice) {
 8         System.out.println("恭喜!老客戶享有9折優惠!");
 9         originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
10         return originalPrice;
11     }
12 }

VIP客戶報價策略實現:

package strategy.examp02;

import java.math.BigDecimal;
//VIP客戶的報價策略實現
public class VIPCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("恭喜!VIP客戶享有8折優惠!");
        originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }
}

報價上下文:

 1 package strategy.examp02;
 2 
 3 import java.math.BigDecimal;
 4 //報價上下文角色
 5 public class QuoteContext {
 6     //持有一個具體的報價策略
 7     private IQuoteStrategy quoteStrategy;
 8 
 9     //注入報價策略
10     public QuoteContext(IQuoteStrategy quoteStrategy){
11         this.quoteStrategy = quoteStrategy;
12     }
13 
14     //回調具體報價策略的方法
15     public BigDecimal getPrice(BigDecimal originalPrice){
16         return quoteStrategy.getPrice(originalPrice);
17     }
18 }

外部客戶端:

 1 package strategy.examp02;
 2 
 3 import java.math.BigDecimal;
 4 //外部客戶端
 5 public class Client {
 6     public static void main(String[] args) {
 7         //1.建立老客戶的報價策略
 8         IQuoteStrategy oldQuoteStrategy = new OldCustomerQuoteStrategy();
 9 
10         //2.建立報價上下文對象,並設置具體的報價策略
11         QuoteContext quoteContext = new QuoteContext(oldQuoteStrategy);
12 
13         //3.調用報價上下文的方法
14         BigDecimal price = quoteContext.getPrice(new BigDecimal(100));
15 
16         System.out.println("折扣價爲:" +price);
17     }
18 }

控制檯輸出:

恭喜!老客戶享有9折優惠!
折扣價爲:90.00

 

這個時候,商場營銷部新推出了一個客戶類型--MVP用戶(Most Valuable Person),能夠享受折扣7折優惠,那該怎麼辦呢?

這個很容易,只要新增一個報價策略的實現,而後外部客戶端調用的時候,建立這個新增的報價策略實現,並設置到策略上下文就能夠了,對原來已經實現的代碼沒有任何的改動。

MVP用戶的報價策略實現:

 1 package strategy.examp02;
 2 
 3 import java.math.BigDecimal;
 4 //MVP客戶的報價策略實現
 5 public class MVPCustomerQuoteStrategy implements IQuoteStrategy {
 6     @Override
 7     public BigDecimal getPrice(BigDecimal originalPrice) {
 8         System.out.println("哇偶!MVP客戶享受7折優惠!!!");
 9         originalPrice = originalPrice.multiply(new BigDecimal(0.7)).setScale(2,BigDecimal.ROUND_HALF_UP);
10         return originalPrice;
11     }
12 }

外部客戶端:

 1 package strategy.examp02;
 2 
 3 import java.math.BigDecimal;
 4 //外部客戶端
 5 public class Client {
 6     public static void main(String[] args) {
 7         //建立MVP客戶的報價策略
 8         IQuoteStrategy mvpQuoteStrategy = new MVPCustomerQuoteStrategy();
 9 
10         //建立報價上下文對象,並設置具體的報價策略
11         QuoteContext quoteContext = new QuoteContext(mvpQuoteStrategy);
12 
13         //調用報價上下文的方法
14         BigDecimal price = quoteContext.getPrice(new BigDecimal(100));
15 
16         System.out.println("折扣價爲:" +price);
17     }
18 }

控制檯輸出:

哇偶!MVP客戶享受7折優惠!!!
折扣價爲:70.00

 

深刻理解策略模式:

策略模式的做用:就是把具體的算法實現從業務邏輯中剝離出來,成爲一系列獨立算法類,使得它們能夠相互替換。

策略模式的着重點:不是如何來實現算法,而是如何組織和調用這些算法,從而讓咱們的程序結構更加的靈活、可擴展。

  咱們前面的第一個報價管理的示例,發現每一個策略算法實現對應的都是在QuoteManager 中quote方法中的if else語句裏面,咱們知道if else if語句裏面的代碼在執行的可能性方面能夠說是平等的,你要麼執行if,要麼執行else,要麼執行else if。

  策略模式就是把各個平等的具體實現進行抽象、封裝成爲獨立的算法類,而後經過上下文和具體的算法類來進行交互。各個策略算法都是平等的,地位是同樣的,正是因爲各個算法的平等性,因此它們纔是能夠相互替換的。雖然咱們能夠動態的切換各個策略,可是同一時刻只能使用一個策略。

  在這個點上,咱們舉個歷史上有名的故事做爲示例:

三國劉備取西川時,謀士龐統給的上、中、下三個計策:

  上策:挑選精兵,晝夜兼行直接偷襲成都,能夠一舉而定,此爲上計計也。

  中策:楊懷、高沛是蜀中名將,手下有精銳部隊,並且據守關頭,咱們能夠裝做要回荊州,引他們輕騎來見,可就此將其擒殺,然後進兵成都,此爲中計。

  下策:退還白帝,連引荊州,慢慢進圖益州,此爲下計。
  這三個計策都是取西川的計策,也就是攻取西川這個問題的具體的策略算法,劉備能夠採用上策,能夠採用中策,固然也能夠採用下策,因而可知策略模式的各類具體的策略算法都是平等的,能夠相互替換。

  那誰來選擇具體採用哪一種計策(算法)?

在這個故事中固然是劉備選擇了,也就是外部的客戶端選擇使用某個具體的算法,而後把該算法(計策)設置到上下文當中;

還有一種狀況就是客戶端不選擇具體的算法,把這個事交給上下文,這至關於劉備說我無論有哪些攻取西川的計策,我只要結果(成功的拿下西川),具體怎麼攻佔(有哪些計策,怎麼選擇)由參謀部來決定(上下文)。

下面咱們演示下這種情景:

1 //攻取西川的策略
2 public interface IOccupationStrategyWestOfSiChuan {
3     public void occupationWestOfSiChuan(String msg);
4 }
 1 //攻取西川的上上計策
 2 public class UpperStrategy implements IOccupationStrategyWestOfSiChuan {
 3     @Override
 4     public void occupationWestOfSiChuan(String msg) {
 5         if (msg == null || msg.length() < 5) {
 6             //故意設置障礙,致使上上計策失敗
 7             System.out.println("因爲計劃泄露,上上計策失敗!");
 8             int i = 100/0;
 9         }
10         System.out.println("挑選精兵,晝夜兼行直接偷襲成都,能夠一舉而定,此爲上計計也!");
11     }
12 }
1 //攻取西川的中計策
2 public class MiddleStrategy implements IOccupationStrategyWestOfSiChuan {
3     @Override
4     public void occupationWestOfSiChuan(String msg) {
5         System.out.println("楊懷、高沛是蜀中名將,手下有精銳部隊,並且據守關頭,咱們能夠裝做要回荊州,引他們輕騎來見,可就此將其擒殺,然後進兵成都,此爲中計。");
6     }
7 }
1 //攻取西川的下計策
2 public class LowerStrategy implements IOccupationStrategyWestOfSiChuan {
3     @Override
4     public void occupationWestOfSiChuan(String msg) {
5         System.out.println("退還白帝,連引荊州,慢慢進圖益州,此爲下計。");
6     }
7 }
 1 //攻取西川參謀部,就是上下文啦,由上下文來選擇具體的策略
 2 public class OccupationContext  {
 3 
 4     public void occupationWestOfSichuan(String msg){
 5         //先用上上計策
 6         IOccupationStrategyWestOfSiChuan strategy = new UpperStrategy();
 7         try {
 8             strategy.occupationWestOfSiChuan(msg);
 9         } catch (Exception e) {
10             //上上計策有問題行不通以後,用中計策
11             strategy = new MiddleStrategy();
12             strategy.occupationWestOfSiChuan(msg);
13         }
14     }
15 }
 1 //此時外部客戶端至關於劉備了,無論具體採用什麼計策,只要結果(成功的攻下西川)
 2 public class Client {
 3 
 4     public static void main(String[] args) {
 5         OccupationContext context = new  OccupationContext();
 6         //這個給手下的人激勵不夠啊
 7         context.occupationWestOfSichuan("拿下西川");
 8         System.out.println("=========================");
 9         //這我的人有賞,讓士兵有動力啊
10         context.occupationWestOfSichuan("拿下西川以後,人人有賞!");
11     }
12 }

控制檯輸出:

因爲計劃泄露,上上計策失敗!
楊懷、高沛是蜀中名將,手下有精銳部隊,並且據守關頭,咱們能夠裝做要回荊州,引他們輕騎來見,可就此將其擒殺,然後進兵成都,此爲中計。
=========================
挑選精兵,晝夜兼行直接偷襲成都,能夠一舉而定,此爲上計計也!


  咱們上面的策略接口採用的是接口的形式來定義的,其實這個策略接口,是廣義上的接口,不是語言層面的interface,也能夠是一個抽象類,若是多個算法具備公有的數據,則能夠將策略接口設計爲一個抽象類,把公共的東西放到抽象類裏面去。

 

 策略和上下文的關係:

   在策略模式中,通常狀況下都是上下文持有策略的引用,以進行對具體策略的調用。但具體的策略對象也能夠從上下文中獲取所需數據,能夠將上下文當作參數傳入到具體策略中,具體策略經過回調上下文中的方法來獲取其所須要的數據。

下面咱們演示這種狀況:

  在跨國公司中,通常都會在各個國家和地區設置分支機構,聘用當地人爲員工,這樣就有這樣一個須要:每個月發工資的時候,中國國籍的員工要發人民幣,美國國籍的員工要發美圓,英國國籍的要發英鎊。

1 //支付策略接口
2 public interface PayStrategy {
3     //在支付策略接口的支付方法中含有支付上下文做爲參數,以便在具體的支付策略中回調上下文中的方法獲取數據
4     public void pay(PayContext ctx);
5 }
1 //人民幣支付策略
2 public class RMBPay implements PayStrategy {
3     @Override
4     public void pay(PayContext ctx) {
5         System.out.println("如今給:"+ctx.getUsername()+" 人民幣支付 "+ctx.getMoney()+"元!");
6     }
7 }
1 //美金支付策略
2 public class DollarPay implements PayStrategy {
3     @Override
4     public void pay(PayContext ctx) {
5         System.out.println("如今給:"+ctx.getUsername()+" 美金支付 "+ctx.getMoney()+"dollar !");
6     }
7 }
 1 //支付上下文,含有多個算法的公有數據
 2 public class PayContext {
 3     //員工姓名
 4     private String username;
 5     //員工的工資
 6     private double money;
 7     //支付策略
 8     private PayStrategy payStrategy;
 9 
10     public void pay(){
11         //調用具體的支付策略來進行支付
12         payStrategy.pay(this);
13     }
14 
15     public PayContext(String username, double money, PayStrategy payStrategy) {
16         this.username = username;
17         this.money = money;
18         this.payStrategy = payStrategy;
19     }
20 
21     public String getUsername() {
22         return username;
23     }
24 
25     public double getMoney() {
26         return money;
27     }
28 }
 1 //外部客戶端
 2 public class Client {
 3     public static void main(String[] args) {
 4         //建立具體的支付策略
 5         PayStrategy rmbStrategy = new RMBPay();
 6         PayStrategy dollarStrategy = new DollarPay();
 7         //準備小王的支付上下文
 8         PayContext ctx = new PayContext("小王",30000,rmbStrategy);
 9         //向小王支付工資
10         ctx.pay();
11 
12         //準備Jack的支付上下文
13         ctx = new PayContext("jack",10000,dollarStrategy);
14         //向Jack支付工資
15         ctx.pay();
16     }
17 }

控制檯輸出:

如今給:小王 人民幣支付 30000.0元!
如今給:jack 美金支付 10000.0dollar !

 

那如今咱們要新增一個銀行帳戶的支付策略,該怎麼辦呢?

  顯然咱們應該新增一個支付找銀行帳戶的策略實現,因爲須要從上下文中獲取數據,爲了避免修改已有的上下文,咱們能夠經過繼承已有的上下文來擴展一個新的帶有銀行帳戶的上下文,而後再客戶端中使用新的策略實現和帶有銀行帳戶的上下文,這樣以前已有的實現徹底不須要改動,遵照了開閉原則。

1 //銀行帳戶支付
2 public class AccountPay implements PayStrategy {
3     @Override
4     public void pay(PayContext ctx) {
5         PayContextWithAccount ctxAccount = (PayContextWithAccount) ctx;
6         System.out.println("如今給:"+ctxAccount.getUsername()+"的帳戶:"+ctxAccount.getAccount()+" 支付工資:"+ctxAccount.getMoney()+" 元!");
7     }
8 }
 1 //帶銀行帳戶的支付上下文
 2 public class PayContextWithAccount extends PayContext {
 3     //銀行帳戶
 4     private String account;
 5     public PayContextWithAccount(String username, double money, PayStrategy payStrategy,String account) {
 6         super(username, money, payStrategy);
 7         this.account = account;
 8     }
 9 
10     public String getAccount() {
11         return account;
12     }
13 }
 1 //外部客戶端
 2 public class Client {
 3     public static void main(String[] args) {
 4         //建立具體的支付策略
 5         PayStrategy rmbStrategy = new RMBPay();
 6         PayStrategy dollarStrategy = new DollarPay();
 7         //準備小王的支付上下文
 8         PayContext ctx = new PayContext("小王",30000,rmbStrategy);
 9         //向小王支付工資
10         ctx.pay();
11         //準備Jack的支付上下文
12         ctx = new PayContext("jack",10000,dollarStrategy);
13         //向Jack支付工資
14         ctx.pay();
15         //建立支付到銀行帳戶的支付策略
16         PayStrategy accountStrategy = new AccountPay();
17         //準備帶有銀行帳戶的上下文
18         ctx = new PayContextWithAccount("小張",40000,accountStrategy,"1234567890");
19         //向小張的帳戶支付
20         ctx.pay();
21     }
22 }

控制檯輸出:

如今給:小王 人民幣支付 30000.0元!
如今給:jack 美金支付 10000.0dollar !
如今給:小張的帳戶:1234567890 支付工資:40000.0 元!

 

  除了上面的方法,還有其餘的實現方式嗎?

  固然有了,上面的實現方式是策略實現所須要的數據都是從上下文中獲取,所以擴展了上下文;如今咱們能夠不擴展上下文,直接從策略實現內部來獲取數據,看下面的實現:

 1 //支付到銀行帳戶的策略
 2 public class AccountPay2 implements PayStrategy {
 3     //銀行帳戶
 4     private String account;
 5     public AccountPay2(String account) {
 6         this.account = account;
 7     }
 8     @Override
 9     public void pay(PayContext ctx) {
10         System.out.println("如今給:"+ctx.getUsername()+"的帳戶:"+getAccount()+" 支付工資:"+ctx.getMoney()+" 元!");
11     }
12     public String getAccount() {
13         return account;
14     }
15     public void setAccount(String account) {
16         this.account = account;
17     }
18 }
 1 //外部客戶端
 2 public class Client {
 3     public static void main(String[] args) {
 4         //建立具體的支付策略
 5         PayStrategy rmbStrategy = new RMBPay();
 6         PayStrategy dollarStrategy = new DollarPay();
 7         //準備小王的支付上下文
 8         PayContext ctx = new PayContext("小王",30000,rmbStrategy);
 9         //向小王支付工資
10         ctx.pay();
11         //準備Jack的支付上下文
12         ctx = new PayContext("jack",10000,dollarStrategy);
13         //向Jack支付工資
14         ctx.pay();
15         //建立支付到銀行帳戶的支付策略
16         PayStrategy accountStrategy = new AccountPay2("1234567890");
17         //準備上下文
18         ctx = new PayContext("小張",40000,accountStrategy);
19         //向小張的帳戶支付
20         ctx.pay();
21     }
22 }

控制檯輸出:

如今給:小王 人民幣支付 30000.0元!
如今給:jack 美金支付 10000.0dollar !
如今給:小張的帳戶:1234567890 支付工資:40000.0 元!

  那咱們來比較一下上面兩種實現方式:

  擴展上下文的實現:

    優勢:具體的策略實現風格非常統一,策略實現所須要的數據都是從上下文中獲取的,在上下文中添加的數據,能夠視爲公共的數據,其餘的策略實現也可使用。

    缺點:很明顯若是某些數據只是特定的策略實現須要,大部分的策略實現不須要,那這些數據有「浪費」之嫌,另外若是每次添加算法數據都擴展上下文,很容易致使上下文的層級非常複雜。

  在具體的策略實現上添加所須要的數據的實現:

    優勢:容易想到,實現簡單

    缺點:與其餘的策略實現風格不一致,其餘的策略實現所需數據都是來自上下文,而這個策略實現一部分數據來自於自身,一部分數據來自於上下文;外部在使用這個策略實現的時候也和其餘的策略實現不一致了,難以以一個統一的方式動態的切換策略實現。

策略模式在JDK中的應用:

  在多線程編程中,咱們常用線程池來管理線程,以減緩線程頻繁的建立和銷燬帶來的資源的浪費,在建立線程池的時候,常用一個工廠類來建立線程池Executors,實際上Executors的內部使用的是類ThreadPoolExecutor.它有一個最終的構造函數以下:

 1     public ThreadPoolExecutor(int corePoolSize,
 2                               int maximumPoolSize,
 3                               long keepAliveTime,
 4                               TimeUnit unit,
 5                               BlockingQueue<Runnable> workQueue,
 6                               ThreadFactory threadFactory,
 7                               RejectedExecutionHandler handler) {
 8         if (corePoolSize < 0 ||
 9             maximumPoolSize <= 0 ||
10             maximumPoolSize < corePoolSize ||
11             keepAliveTime < 0)
12             throw new IllegalArgumentException();
13         if (workQueue == null || threadFactory == null || handler == null)
14             throw new NullPointerException();
15         this.corePoolSize = corePoolSize;
16         this.maximumPoolSize = maximumPoolSize;
17         this.workQueue = workQueue;
18         this.keepAliveTime = unit.toNanos(keepAliveTime);
19         this.threadFactory = threadFactory;
20         this.handler = handler;
21     }

corePoolSize:線程池中的核心線程數量,即便這些線程沒有任務幹,也不會將其銷燬。

maximumPoolSize:線程池中的最多可以建立的線程數量。

keepAliveTime:當線程池中的線程數量大於corePoolSize時,多餘的線程等待新任務的最長時間。

unitkeepAliveTime的時間單位。

workQueue:在線程池中的線程尚未還得及執行任務以前,保存任務的隊列(當線程池中的線程都有任務在執行的時候,仍然有任務不斷的提交過來,這些任務保存在workQueue隊列中)。

threadFactory:建立線程池中線程的工廠。

handler:當線程池中沒有多餘的線程來執行任務,而且保存任務的多列也滿了(指的是有界隊列),對仍在提交給線程池的任務的處理策略。

RejectedExecutionHandler 是一個策略接口,用在當線程池中沒有多餘的線程來執行任務,而且保存任務的多列也滿了(指的是有界隊列),對仍在提交給線程池的任務的處理策略。
public interface RejectedExecutionHandler {

    /**
     *當ThreadPoolExecutor的execut方法調用時,而且ThreadPoolExecutor不能接受一個任務Task時,該方法就有可能被調用。
   * 不能接受一個任務Task的緣由:有多是沒有多餘的線程來處理,有多是workqueue隊列中沒有多餘的位置來存放該任務,該方法有可能拋出一個未受檢的異常RejectedExecutionException
     * @param r the runnable task requested to be executed
     * @param executor the executor attempting to execute this task
     * @throws RejectedExecutionException if there is no remedy
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

該策略接口有四個實現類:

  AbortPolicy:該策略是直接將提交的任務拋棄掉,並拋出RejectedExecutionException異常。

    /**
     * A handler for rejected tasks that throws a
     * <tt>RejectedExecutionException</tt>.
     */
    public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an <tt>AbortPolicy</tt>.
         */
        public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always.
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException();
        }
    }

  

  DiscardPolicy:該策略也是將任務拋棄掉(對於提交的任務無論不問,什麼也不作),不過並不拋出異常。

    /**
     * A handler for rejected tasks that silently discards the
     * rejected task.
     */
    public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a <tt>DiscardPolicy</tt>.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

  

  DiscardOldestPolicy:該策略是當執行器未關閉時,從任務隊列workQueue中取出第一個任務,並拋棄這第一個任務,進而有空間存儲剛剛提交的任務。使用該策略要特別當心,由於它會直接拋棄以前的任務。

 /**
     * A handler for rejected tasks that discards the oldest unhandled
     * request and then retries <tt>execute</tt>, unless the executor
     * is shut down, in which case the task is discarded.
     */
    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a <tt>DiscardOldestPolicy</tt> for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
         * Obtains and ignores the next task that the executor
         * would otherwise execute, if one is immediately available,
         * and then retries execution of task r, unless the executor
         * is shut down, in which case task r is instead discarded.
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

  

  CallerRunsPolicy:該策略並無拋棄任何的任務,因爲線程池中已經沒有了多餘的線程來分配該任務,該策略是在當前線程(調用者線程)中直接執行該任務。

  /**
     * A handler for rejected tasks that runs the rejected task
     * directly in the calling thread of the {@code execute} method,
     * unless the executor has been shut down, in which case the task
     * is discarded.
     */
    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

類ThreadPoolExecutor中持有一個RejectedExecutionHandler接口的引用,以便在構造函數中能夠由外部客戶端本身制定具體的策略並注入。下面看一下其類圖:

                                        

 

策略模式的優勢:

  1.策略模式的功能就是經過抽象、封裝來定義一系列的算法,使得這些算法能夠相互替換,因此爲這些算法定義一個公共的接口,以約束這些算法的功能實現。若是這些算法具備公共的功能,能夠將接口變爲抽象類,將公共功能放到抽象父類裏面。

  2.策略模式的一系列算法是能夠相互替換的、是平等的,寫在一塊兒就是if-else組織結構,若是算法實現裏又有條件語句,就構成了多重條件語句,能夠用策略模式,避免這樣的多重條件語句。

  3.擴展性更好:在策略模式中擴展策略實現很是的容易,只要新增一個策略實現類,而後在使用策略實現的地方,使用這個新的策略實現就行了。

 

策略模式的缺點:

    1.客戶端必須瞭解全部的策略,清楚它們的不一樣:

     若是由客戶端來決定使用何種算法,那客戶端必須知道全部的策略,清楚各個策略的功能和不一樣,這樣才能作出正確的選擇,可是這暴露了策略的具體實現。

  2.增長了對象的數量:

    因爲策略模式將每一個具體的算法都單獨封裝爲一個策略類,若是可選的策略有不少的話,那對象的數量也會不少。

  3.只適合偏平的算法結構:

    因爲策略模式的各個策略實現是平等的關係(可相互替換),實際上就構成了一個扁平的算法結構。即一個策略接口下面有多個平等的策略實現(多個策略實現是兄弟關係),而且運行時只能有一個算法被使用。這就限制了算法的使用層級,且不能被嵌套。

 

策略模式的本質:

  分離算法,選擇實現。

  若是你仔細思考策略模式的結構和功能的話,就會發現:若是沒有上下文,策略模式就回到了最基本的接口和實現了,只要是面向接口編程,就可以享受到面向接口編程帶來的好處,經過一個統一的策略接口來封裝和分離各個具體的策略實現,無需關係具體的策略實現。

  貌似沒有上下文什麼事,可是若是沒有上下文的話,客戶端就必須直接和具體的策略實現進行交互了,尤爲是須要提供一些公共功能或者是存儲一些狀態的時候,會大大增長客戶端使用的難度;引入上下文以後,這部分工做能夠由上下文來完成,客戶端只須要和上下文進行交互就能夠了。這樣可讓策略模式更具備總體性,客戶端也更加的簡單。

  策略模式體現了開閉原則:策略模式把一系列的可變算法進行封裝,從而定義了良好的程序結構,在出現新的算法的時候,能夠很容易的將新的算法實現加入到已有的系統中,而已有的實現不須要修改。

  策略模式體現了里氏替換原則:策略模式是一個扁平的結構,各個策略實現都是兄弟關係,實現了同一個接口或者繼承了同一個抽象類。這樣只要使用策略的客戶端保持面向抽象編程,就能夠動態的切換不一樣的策略實現以進行替換。

相關文章
相關標籤/搜索