Strategy模式,對一系列的算法加以封裝,爲全部算法定義一個抽象的算法接口,並經過繼承該抽象算法接口對全部的算法加以封裝和實現,具體的算法選擇交由客戶端決定(策略)。
Strategy模式主要用於平滑的處理算法的切換
在軟件構建過程當中,某些對象可能用到的算法多種多樣,常常改變,若是將這些算法都編碼到對象中,將會使得對象變得異常複雜;並且有時候支持不使用的算法也是一個性能負擔。
如何在運行時根據須要透明地更改對象的算法?將算法與對象自己解耦,從而避免上述問題?
enum TaxBase { CN_Tax, US_Tax, DE_Tax, }; class SalesOrder{ TaxBase tax; public: double CalculateTax(){ //... if (tax == CN_Tax){ //或者switch開關語句 //CN*********** } else if (tax == US_Tax){ //US*********** } else if (tax == DE_Tax){ //DE*********** }//.... } };
不要靜態的去看一個軟件結構的設計,而是要動態的去看,這是設計模式的一個重要觀點。
enum TaxBase { CN_Tax, US_Tax, DE_Tax, FR_Tax //更改 }; class SalesOrder{ TaxBase tax; public: double CalculateTax(){ //... if (tax == CN_Tax){ //CN*********** } else if (tax == US_Tax){ //US*********** } else if (tax == DE_Tax){ //DE*********** } else if (tax == FR_Tax){ //更改 //... } //.... } };
對擴展開發,對更改封閉。類模塊儘量用擴展的方式來支持將來的變化,而不是找到源代碼,用修改源代碼的方式來面對將來的變化
上面代碼兩處紅色部分就違反了開放封閉原則,帶來了一個很大的複用性負擔,軟件要從新更改,從新編譯,從新測試,從新部署,須要的代價十分大。
因此儘量使用擴展方式來解決問題,如何使用擴展方式來解決,就要用到Strategy模式
class TaxStrategy{ //算法測試基類 public: virtual double Calculate(const Context& context)=0; //純虛方法 virtual ~TaxStrategy(){} //虛析構函數 }; class CNTax : public TaxStrategy{ public: virtual double Calculate(const Context& context){ //*********** } }; class USTax : public TaxStrategy{ public: virtual double Calculate(const Context& context){ //*********** } }; class DETax : public TaxStrategy{ public: virtual double Calculate(const Context& context){ //*********** } }; class SalesOrder{ private: TaxStrategy* strategy; //多態性 public: SalesOrder(StrategyFactory* strategyFactory){ this->strategy = strategyFactory->NewStrategy(); //工廠模式來建立指針,堆對象, } ~SalesOrder(){ delete this->strategy; } public double CalculateTax(){ //... Context context(); //上下文參數 double val = strategy->Calculate(context); //多態調用,具體依賴於工廠建立的對象 //... } };
咱們將原來結構化設計中的一個個算法,變爲TaxStrategy類的一個子類
相對於上面結構化思想相比較:功能是同樣的。可是要比較好處,在設計領域要比較好處須要放在時間軸中去看
//擴展 //********************************* class FRTax : public TaxStrategy{ public: virtual double Calculate(const Context& context){ //......... } };
其餘地方不須要修改,固然工廠中須要去修改使得可以產生法國稅務對象
多態調用會天然找到咱們擴展的稅務算法運算
在總體代碼中,尤爲是SalesOrder主調用類中,代碼不須要改變,獲得了複用性
注意:咱們在新的文件中寫入咱們擴展的算法,單獨編譯,例如dll,動態加入程序中,遵循了開閉原則
而第一種方法中在方法內部去修改添加源代碼,打破了開放封閉原則,違背了複用性
注意:咱們在面向對象中(尤爲是設計模式領域中)談到的複用性是指編譯單位,二進制層次上的複用性
例如咱們第二種方法,在編譯部署後,不須要修改原來的文件,咱們只須要將新添加的擴展單獨編譯,動態被調用便可。原來二進制文件的複用性很好。
可是對於第一種方法,咱們對原方法進行了修改,咱們的整個程序都要從新編譯,部署,原來的二進制文件再也不使用,而是須要咱們新的編譯後的二進制文件,因此複用性極差
例以下面的修改:算法
if (tax == CN_Tax){ //CN*********** } else if (tax == US_Tax){ //US*********** } else if (tax == DE_Tax){ //DE*********** } else if (tax == FR_Tax){ //更改 //... }
並且在下面補充一段代碼是頗有問題的,雖然咱們保證上面代碼不變,可是在實際開發中,向後面添加代碼,每每會打破這個代碼前面的一些代碼,會向前面引入一些bug。並且咱們對這種不叫作複用,真正的複用是編譯層面的
定義一系列算法,把他們一個個封裝起來,而且使他們能夠互相替換(變化<各個算法>)。該模式使得算法可獨立於使用它的客戶程序(穩定<SalesOrder類>)而變化(擴展,子類化)
Strategy基類中通常放置的方法很少
除了極個別,像單例。其餘的均可以像上面同樣去找出它的穩定部分和變化部分
class SalesOrder{ private: TaxStrategy* strategy; public: SalesOrder(StrategyFactory* strategyFactory){ this->strategy = strategyFactory->NewStrategy(); //在運行時傳入一個多態對象 } ~SalesOrder(){ delete this->strategy; } public double CalculateTax(){ //... Context context(); double val = strategy->Calculate(context); //在運行時就支持多態調用,靈活變化 //... } };
通常來講代碼出現if..else...或者switch...case...,這就是咱們須要Strategy模式的特徵。(固然咱們說的是變化的,如果像男女性別判斷,就是絕對不變的,就不須要使用到Strategy模式,可是更多場景都是可變的) 由於if..else..是結構化思惟中的分而治之。並且須要一直判斷,支持不使用的算法也是一個性能負擔。並且由很大一段代碼都被裝載在代碼段中,這是不須要的。 咱們使用Strategy模式,是在運行時加載,運行中須要哪一個就能夠即時調用。具備穩定性。 在運行時代碼在代碼段中,放在內存中,最好的是加載在高級緩存中,最快,如果代碼段過長,就只能放在主存,甚至虛擬內存中(硬盤),因此咱們使用if...else..時,會有不少代碼被加載到高級緩存,內存中,這會佔用空間,其餘代碼就會被擠入其餘地方,執行不會太快,Strategy模式會順便解決部分這個問題
//策略基類 class Strategy { public: virtual void SymEncrypt() = 0; virtual ~Strategy(){}; };
//各個算法 class Des:public Strategy { public: virtual void SymEncrypt() { cout << "Des Encrypt" << endl; } }; class AES :public Strategy { public: virtual void SymEncrypt() { cout << "AES Encrypt" << endl; } };
//上下文管理器 class Context { private: Strategy* s; public: Context(Strategy* strategy) { s = strategy; //這裏應該由工廠實現 } void Operator() { s->SymEncrypt(); } };
void main() { Strategy* strategy = NULL; Context* ctx = NULL; strategy = new AES; ctx = new Context(strategy); ctx->Operator(); delete strategy; delete ctx; system("pause"); return; }