(Boolan)C++設計模式 ——策略模式(Strategy)

weixin_34208283發表於2017-06-28

定義一系列演算法,把他們一個個封裝起來,並且是他們可以互相替換(變化)。該模式似的演算法可以獨立於使用它的客戶程式(穩定的)而變化(擴充套件,子類化)。
——《設計模式》GoF

  • 動機(Motivation)
    在軟體構建的過程中,某些物件使用的演算法可能是多種多樣的,經常改變,如果將這些演算法都編碼到物件中,將會使物件變得異常複雜;而且有時候支援不使用的演算法也是一種效能負擔

比如現在需要有一個稅種的計算系統,其中支援計算各國的稅種,那麼他的偽碼描述就是如下:

enum TaxBase {
    CN_Tax,
    US_Tax,
    DE_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***********
        }
        //....
     }
    
};

但是隨著時間的推移,這些稅種對我們來說已經不能滿足我們的要求了,比如需要增加一個稅種的計算,那麼我們的偽碼恐怕會程式設計以下這樣了

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){  //更改
            //...
        }

        //....
     }
    
};

我們不但需要修改TaxBase中的引數,還需要在SalseOrder中新增關於新增加稅種的一個判斷,然後才能進行相應的計算。

對於上面這個偽碼描述來說,修改的地方也是比較多的,基本上是牽一髮而動全身了。並且,在SalesOrder中,CalculatetTax函式中每次呼叫其實只會使用其中的一個分支,那麼其餘的分支每次也需要被載入到記憶體中去,其實也會是一種資源的浪費。

也就是他違反了對開閉原則,同時也違反了反向依賴的原則。

那麼我們嘗試另外一種方式

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); //多型呼叫
        //...
    }
    
};

那麼此時如果需要增加一個新的稅種,該如何增加呢,那麼偽碼描述就下下方了。


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 FRTax : 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); //多型呼叫
        //...
    }
    
};

對於第二個方法來說,SalesOrder的CalculateTax函式來說,他多型呼叫了TaxStrategy類中的Calucate方法。而TaxStrategy來說他的每個子類都是一種新的稅種計算方式,每個稅種計算中還實現了自己對應的稅種計算的方法。
在擴充套件的時候,需要增加TaxStrategy的子類就行了,不需要在改變SalesOrder的中的程式碼了。

5688965-ed49a0f695e749f9.png
Strategy模式的UML

要點總結

  1. Strategy及其子類為元件提供了一系列可重用的演算法,從而可以使得型別在執行時方便的根據需要在各個演算法之間進行切換
  • Strategy模式提供了用條件判斷語句的另一種選擇,消除條件判斷語句,就是在解耦合。含有許多條件判斷語句的程式碼通常都需要Strategy模式。尤其是條件判斷語句在未來會有增加可能性的時候,應該優先考慮Strategy模式。
  • 如果Strategy物件沒有例項變數,那麼各個上下文可以共享同一個Strategy物件,從而節省物件的開銷。

相關文章