策略模式原理及案例分析
策略模式的正式定義為:它定義了一個演算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓演算法的變化獨立於使用演算法的客戶。
這個概念畢竟是對這個模式的高度總結,我們可以先不必瞭解其含義,看完下面內容後,大家可以回頭再揣摩一下定義的奧妙之處。
我先來舉一個生活中的例項:
如果現在我需要寫一個關於飛機行為的類,暫且描述飛機的三個行為動作:
1.可以飛;
2.有兩個機翼;
3.客運功能。
我們會想到建一個飛機類Plane,之後建各種飛機子類來繼承父類就好了。但是如果只是單純的繼承父類,飛機子類真的還能適用嗎?對於可以飛和有兩個機翼基本上飛機都可以滿足,但是對於飛機的功能很明顯是不可以的,比如說偵察機是沒有客運功能的,戰鬥機也是沒有客運功能的。
解決方案:
1.可以讓子類來重寫父類的功能方法(這種方法很明顯是不可取的,如果說飛機種類很多,你不得不為每一個種類的飛機都要重寫這樣的一個方法,很重要的一點是,對於同樣有戰鬥功能的戰鬥機和轟炸機,你卻不得不重寫兩遍一模一樣的功能函式,如果有10個、20個、100個相同功能的不同子類,你也要硬著頭皮寫下去嗎?)
2.可能大家會覺得,或許一開始父類Plane的類結構或許就已經存在問題,但是如果去掉這一個行為,那麼每個子類還是要去新增這樣一個方法,這樣就回到了解決方案1的弊端上去了。更何況,飛機的功能原本就是每個飛機要有的基本屬性,抽象到父類從邏輯上講完全行得通。
3.策略模式登場!解決方案2其實已經有點接近了,不過不是去掉這一個方法,而是要將此方法抽象出來,不表示具體某一功能。
我們結合程式碼一起探討。首先建立一個父類Plane。
public class Plane { Function function; public void fly(){ System.out.println("can fly!"); } public void appearance(){ System.out.println("has two wings"); } public void performFunction(){ function.whatFunction(); } }
注意:在這個父類裡,我們將功能Function抽象成了一個介面類,來表示功能這一行為,把介面類作為Plane類的一個變數。(通常我更喜歡稱之為行為類)
Function類如下:
public interface Function {
void whatFunction();
}
這個就是一個簡單的行為類,具體的行為需要來實現這個介面,比如客運功能和偵查功能等等。
public class AircraftFunction implements Function {
public void whatFunction(){
System.out.println("can carry guest");
}
}
public class InvestigationFunction implements Function{
public void whatFunction(){
System.out.println("can investigation enemy");
}
}
我們將所有的準備工作做好,現在就可以建立子類了。這裡建立客運機和偵察機兩個子類。在初始化時就賦予它們Function。
public class AircraftPlane extends Plane{
public AircraftPlane() {
this.function = new AircraftFunction();
}
}
public class InvestigationPlane extends Plane{
public InvestigationPlane(){
this.function = new InvestigationFunction();
}
}
現在我們就只需要一個執行類,來檢視執行結果就可以了。
public class PlaneSimulator {
public static void main(String[] args) {
Plane aircraft = new AircraftPlane();
System.out.println("======aircraft======");
aircraft.fly();
aircraft.appearance();
aircraft.performFunction();
Plane investigation = new InvestigationPlane();
System.out.println("======investigation======");
investigation.fly();
investigation.appearance();
investigation.performFunction();
}
}
最後結果輸出為:
======aircraft======
can fly!
has two wings
can carry guest
======investigation======
can fly!
has two wings
can investigation enemy
為了使大家更清晰的瞭解整個結構,這裡給出它們的關係類圖
在這個類圖中大家可以看到,飛機的功能已經完全作為一個介面類被抽象了出來,並重新組合進了Plane類中,同時,繼承父類Plane的子類中,也各自組合了功能的介面類的具體實現類。
從巨集觀上看,如果某一行為存在著共性的屬性(只要是飛機都有其存在的功能),卻又因“類“而異,對不同的具體子類會表現出不同的行為,那麼這種情況下應該採用介面的形式來抽象出類的這一行為。這種方法可稱之為:對介面的程式設計。
而對於上述的第1種解決方案,則是要繁瑣得在每一個子類都去具體實現功能,我們可以稱此種方案:依靠實現程式設計。
由此我們引申出:針對介面程式設計,不針對實現程式設計。這是設計模式裡一個非常重要的原則。希望大家可以藉助這個例子有所體會。
在策略模式的解決方案中,對於行為類是採用了組合的形式新增到了父類中,而不是盲目的採用解決方案1中的子類對父類的一味繼承、通過不斷地重複操作來實現功能。由此引出設計模式的另一條重要原則:少用繼承,多用組合。
現在我們再看首段對策略模式的規範性定義:此處的演算法族就是以介面Function為中心的飛機功能的實現類的集合。通過Function介面可以讓Plane的子類來隨意使用這個行為類的各種實現類。而對於這種變化,在Plane父類及它的子類中,卻完全不知情,它們只需要引用這個介面類就可以。
看到這裡想必大家對策略模式已經有了一個比較深刻的認知。我們下面來閒看一點spring原始碼方面的知識點。
在SpringMVC中,Validation介面常被用作校驗實體類。而我們可以建立起UserValidation、ProductValidation來實現Validation介面,並由ValidationUtils來作為執行類。那麼此時這整個的流程就可以看作是策略模式的應用。類圖如下:
這裡先貼出Spring的原始碼:
org.springframework.validation.ValidationUtils(節選)
public static void invokeValidator(Validator validator, Object obj, Errors errors, Object... validationHints) {
Assert.notNull(validator, "Validator must not be null");
Assert.notNull(errors, "Errors object must not be null");
if(logger.isDebugEnabled()) {
logger.debug("Invoking validator [" + validator + "]");
}
if(obj != null && !validator.supports(obj.getClass())) {
throw new IllegalArgumentException("Validator [" + validator.getClass() + "] does not support [" + obj.getClass() + "]");
} else {
if(!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
((SmartValidator)validator).validate(obj, errors, validationHints);
} else {
validator.validate(obj, errors);
}
if(logger.isDebugEnabled()) {
if(errors.hasErrors()) {
logger.debug("Validator found " + errors.getErrorCount() + " errors");
} else {
logger.debug("Validator found no errors");
}
}
}
}
org.springframework.validation.Validator
public interface Validator {
boolean supports(Class<?> var1);
void validate(Object var1, Errors var2);
}
下面為我們的實現類UserValidator和ProductValidator
public class UserValidator implements Validator {
@Override
public boolean supports(Class clazz) {
return User.class.equals(clazz);
}
@Override
public void validate(User user, Errors errors) {
if (!StringUtils.hasLength(user.getUsername())) {
errors.rejectValue("username", "", "使用者名稱不能為空");
}
if (!StringUtils.hasLength(user.getPassword())) {
errors.rejectValue("password", "", "登入密碼不能為空");
}
}
}
public class ProductValidator implements Validator {
@Override
public boolean supports(Class clazz) {
return Product.class.equals(clazz);
}
@Override
public void validate(Product product, Errors errors) {
if (!StringUtils.hasLength(product.getName())) {
errors.rejectValue("name", "", "無效產品");
}
}
}
現在我們可以呼叫各自的策略模式了:
ValidationUtils.invokeValidator(new UserValidator(), user, errors);
ValidationUtils.invokeValidator(new ProductValidator(), Product, errors);
有的讀者可能會產生疑問,這個策略模式怎麼沒有所謂的子父類的關聯呢?策略模式難道不是建立在子父類的關係上嗎?我以前完全地認為策略模式是建立在子父類的基礎上的,包括《Head First之設計模式》最經典的例子Duck,也都是建立在子父類的關係上的。但是也請大家仔細讀讀策略模式的規範性定義,自始至終沒有出現子父類或繼承的字眼。策略模式一定是強調以演算法族,來實現某演算法遊離於使用演算法的客戶之外。這也是我要舉出這個Spring原始碼作為例子的緣由,希望大家可以發現策略模式的本質所在。
好了,以上就是我對策略模式的理解。希望能夠對大家有所幫助。