本系列博客是自己在學習設計模式過程中收集整理的文章集合,其他文章參看設計模式傳送門
本文是轉載文章,原文請參見設計模式(十二)——策略模式
概念
學習過設計模式的人大概都知道Head First設計模式這本書,這本書中介紹的第一個模式就是策略模式。把策略模式放在第一個,筆者認為主要有兩個原因:1、這的確是一個比較簡單的模式。2、這個模式可以充分的體現面向對象設計原則中的封裝變化
、多用組合,少用繼承
、針對接口編程,不針對實現編程
等原則。
策略模式(Strategy Pattern):定義一系列算法,將每一個算法封裝起來,並讓它們可以相互替換。策略模式讓算法獨立於使用它的客戶而變化,也稱為政策模式(Policy)。
用途
結合策略模式的概念,我們找一個實際的場景來理解一下。
假設我們是一家新開的書店,為了招攬顧客,我們推出會員服務,我們把店里的會員分為三種,分別是初級會員、中級會員和高級會員。針對不同級別的會員我們給予不同的優惠。初級會員買書我們不打折、中級會員買書我們打九折、高級會員買書我們打八折。
我們希望用戶在付款的時候,只要刷一下書的條形碼,會員再刷一下他的會員卡,收銀台的工組人員就能直接知道應該向顧客收取多少錢。
在不使用模式的情況下,我們可以在結算的方法中使用if/else
語句來區別出不同的會員來計算價格。
但是,如果我們有一天想要把初級會員的折扣改成9.8折怎么辦?有一天我要推出超級會員怎么辦?有一天我要針對中級會員可打折的書的數量做限制怎么辦?
使用if\else
設計出來的系統,所有的算法都寫在了一起,只要有改動我就要修改整個類。我們都知道,只要是修改代碼就有可能引入問題。為了避免這個問題,我們可以使用策略模式。。。
對於收銀台系統,計算應收款的時候,一個客戶只可能是初級、中級、高級會員中的一種。不同的會員使用不同的算法來計算價格。收銀台系統其實不關心具體的會員類型和折扣之間的關系。也不希望會員和折扣之間的任何改動會影響到收銀台系統。
在介紹策略模式的具體實現方式之前,再來鞏固一下幾個面向對象設計原則:封裝變化
、多用組合,少用繼承
、針對接口編程,不針對實現編程
。想一想如何運用到策略模式中,並且有什么好處。
實現方式
策略模式包含如下角色:
Context: 環境類
Strategy: 抽象策略類
ConcreteStrategy: 具體策略類
我們運用策略模式來實現一下書店的收銀台系統。我們可以把會員抽象成一個策略類,不同的會員類型是具體的策略類。不同策略類里面實現了計算價格這一算法。然后通過組合的方式把會員集成到收銀台中。
先定義一個接口,這個接口就是抽象策略類,該接口定義了計算價格方法,具體實現方式由具體的策略類來定義。
/**
* Created by hollis on 16/9/19. 會員接口
*/
public interface Member {
/**
* 計算應付價格
* @param bookPrice 書籍原價(針對金額,建議使用BigDecimal,double會損失精度)
* @return 應付金額
*/
public double calPrice(double bookPrice);
}
針對不同的會員,定義三種具體的策略類,每個類中都分別實現計算價格方法。
/**
* Created by hollis on 16/9/19. 初級會員
*/
public class PrimaryMember implements Member {
@Override
public double calPrice(double bookPrice) {
System.out.println("對於初級會員的沒有折扣");
return bookPrice;
}
}
/**
* Created by hollis on 16/9/19. 中級會員,買書打九折
*/
public class IntermediateMember implements Member {
@Override
public double calPrice(double bookPrice) {
System.out.println("對於中級會員的折扣為10%");
return bookPrice * 0.9;
}
}
/**
* Created by hollis on 16/9/19. 高級會員,買書打八折
*/
public class AdvancedMember implements Member {
@Override
public double calPrice(double bookPrice) {
System.out.println("對於中級會員的折扣為20%");
return bookPrice * 0.8;
}
}
上面幾個類的定義體現了封裝變化
的設計原則,不同會員的具體折扣方式改變不會影響到其他的會員。
定義好了抽象策略類和具體策略類之后,我們再來定義環境類,所謂環境類,就是集成算法的類。這個例子中就是收銀台系統。采用組合的方式把會員集成進來。
/**
* Created by hollis on 16/9/19. 書籍價格類
*/
public class Cashier {
/**
* 會員,策略對象
*/
private Member member;
public Cashier(Member member){
this.member = member;
}
/**
* 計算應付價格
* @param booksPrice
* @return
*/
public double quote(double booksPrice) {
return this.member.calPrice(booksPrice);
}
}
這個Cashier類就是一個環境類,該類的定義體現了多用組合,少用繼承
、針對接口編程,不針對實現編程
兩個設計原則。由於這里采用了組合+接口的方式,后面我們在推出超級會員的時候無須修改Cashier類。只要再定義一個SuperMember implements Member
就可以了。
下面定義一個客戶端來測試一下:
/**
* Created by hollis on 16/9/19.
*/
public class BookStore {
public static void main(String[] args) {
//選擇並創建需要使用的策略對象
Member strategy = new AdvancedMember();
//創建環境
Cashier cashier = new Cashier(strategy);
//計算價格
double quote = cashier.quote(300);
System.out.println("高級會員圖書的最終價格為:" + quote);
strategy = new IntermediateMember();
cashier = new Cashier(strategy);
quote = cashier.quote(300);
System.out.println("中級會員圖書的最終價格為:" + quote);
}
}
//對於中級會員的折扣為20%
//高級會員圖書的最終價格為:240.0
//對於中級會員的折扣為10%
//中級會員圖書的最終價格為:270.0
從上面的示例可以看出,策略模式僅僅封裝算法,提供新的算法插入到已有系統中,策略模式並不決定在何時使用何種算法。在什么情況下使用什么算法是由客戶端決定的。
- 策略模式的重心
- 策略模式的重心不是如何實現算法,而是如何組織、調用這些算法,從而讓程序結構更靈活,具有更好的維護性和擴展性。
- 算法的平等性
- 策略模式一個很大的特點就是各個策略算法的平等性。對於一系列具體的策略算法,大家的地位是完全一樣的,正因為這個平等性,才能實現算法之間可以相互替換。所有的策略算法在實現上也是相互獨立的,相互之間是沒有依賴的。
- 所以可以這樣描述這一系列策略算法:策略算法是相同行為的不同實現。
- 運行時策略的唯一性
- 運行期間,策略模式在每一個時刻只能使用一個具體的策略實現對象,雖然可以動態地在不同的策略實現中切換,但是同時只能使用一個。
- 公有的行為
- 經常見到的是,所有的具體策略類都有一些公有的行為。這時候,就應當把這些公有的行為放到共同的抽象策略角色Strategy類里面。當然這時候抽象策略角色必須要用Java抽象類實現,而不能使用接口。(《JAVA與模式》之策略模式)
使用枚舉實現策略模式
這個也比較好舉例,比如用枚舉就可以寫出一個基於策略模式的加減乘除計算器
public class Test {
public enum Calculator {
ADDITION {
public Double execute( Double x, Double y ) {
return x + y; // 加法
}
},
SUBTRACTION {
public Double execute( Double x, Double y ) {
return x - y; // 減法
}
},
MULTIPLICATION {
public Double execute( Double x, Double y ) {
return x * y; // 乘法
}
},
DIVISION {
public Double execute( Double x, Double y ) {
return x/y; // 除法
}
};
public abstract Double execute(Double x, Double y);
}
public static void main(String[] args) {
System.out.println( Calculator.ADDITION.execute( 4.0, 2.0 ) );
// 打印 6.0
System.out.println( Calculator.SUBTRACTION.execute( 4.0, 2.0 ) );
// 打印 2.0
System.out.println( Calculator.MULTIPLICATION.execute( 4.0, 2.0 ) );
// 打印 8.0
System.out.println( Calculator.DIVISION.execute( 4.0, 2.0 ) );
// 打印 2.0
}
}
策略模式的優缺點
優點
- 策略模式提供了對“開閉原則”的完美支持,用戶可以在不修改原有系統的基礎上選擇算法或行為,也可以靈活地增加新的算法或行為。
- 策略模式提供了管理相關的算法族的辦法。策略類的等級結構定義了一個算法或行為族。恰當使用繼承可以把公共的代碼移到父類里面,從而避免代碼重復。
- 使用策略模式可以避免使用多重條件(if-else)語句。多重條件語句不易維護,它把采取哪一種算法或采取哪一種行為的邏輯與算法或行為的邏輯混合在一起,統統列在一個多重條件語句里面,比使用繼承的辦法還要原始和落后。
缺點
- 客戶端必須知道所有的策略類,並自行決定使用哪一個策略類。這就意味着客戶端必須理解這些算法的區別,以便適時選擇恰當的算法類。
- 由於策略模式把每個具體的策略實現都單獨封裝成為類,如果備選的策略很多的話,那么對象的數目就會很可觀。可以通過使用享元模式在一定程度上減少對象的數量。