1. 程式人生 > >策略模式原理及案例分析

策略模式原理及案例分析

策略模式的正式定義為:它定義了一個演算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓演算法的變化獨立於使用演算法的客戶。

  這個概念畢竟是對這個模式的高度總結,我們可以先不必瞭解其含義,看完下面內容後,大家可以回頭再揣摩一下定義的奧妙之處。

  我先來舉一個生活中的例項:

  如果現在我需要寫一個關於飛機行為的類,暫且描述飛機的三個行為動作:

  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原始碼作為例子的緣由,希望大家可以發現策略模式的本質所在。

       好了,以上就是我對策略模式的理解。希望能夠對大家有所幫助。