設計模式---組件協做模式之策略模式(Strategy)

一:概念

Strategy模式,對一系列的算法加以封裝,爲全部算法定義一個抽象的算法接口,並經過繼承該抽象算法接口對全部的算法加以封裝和實現,具體的算法選擇交由客戶端決定(策略)。
Strategy模式主要用於平滑的處理算法的切換

二:動機

在軟件構建過程當中,某些對象可能用到的算法多種多樣,常常改變,若是將這些算法都編碼到對象中,將會使得對象變得異常複雜;並且有時候支持不使用的算法也是一個性能負擔。
如何在運行時根據須要透明地更改對象的算法?將算法與對象自己解耦,從而避免上述問題?

三:代碼解析(稅種計算)

(一)結構化代碼

1.原代碼

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***********
        }//....
     }
    
};
不要靜態的去看一個軟件結構的設計,而是要動態的去看,這是設計模式的一個重要觀點。

2.需求變化,須要支持法國稅法

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模式

(二)面向對象Strategy模式代碼

1.原代碼

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類的一個子類
相對於上面結構化思想相比較:功能是同樣的。可是要比較好處,在設計領域要比較好處須要放在時間軸中去看

2.修改代碼,如今須要支持法國稅務算法,進行擴展(新的文件)

//擴展
//*********************************
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基類中通常放置的方法很少
除了極個別,像單例。其餘的均可以像上面同樣去找出它的穩定部分和變化部分

六:要點總結(優勢)

(一)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); //在運行時就支持多態調用,靈活變化 //...
    }
    
};

(二)Strategy模式提供了用條件判斷語句之外的另外一種選擇,消除條判斷語句,就是在解耦合。含有許多條件判斷語句的代碼一般都須要Strategy模式。

通常來講代碼出現if..else...或者switch...case...,這就是咱們須要Strategy模式的特徵。(固然咱們說的是變化的,如果像男女性別判斷,就是絕對不變的,就不須要使用到Strategy模式,可是更多場景都是可變的)
由於if..else..是結構化思惟中的分而治之。並且須要一直判斷,支持不使用的算法也是一個性能負擔。並且由很大一段代碼都被裝載在代碼段中,這是不須要的。
咱們使用Strategy模式,是在運行時加載,運行中須要哪一個就能夠即時調用。具備穩定性。
在運行時代碼在代碼段中,放在內存中,最好的是加載在高級緩存中,最快,如果代碼段過長,就只能放在主存,甚至虛擬內存中(硬盤),因此咱們使用if...else..時,會有不少代碼被加載到高級緩存,內存中,這會佔用空間,其餘代碼就會被擠入其餘地方,執行不會太快,Strategy模式會順便解決部分這個問題

(三)若是Strategy對象沒有實例變量,那麼各個上下文能夠共享同一個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;
}

相關文章
相關標籤/搜索