C++設計模式——策略模式Strategy-Pattern


動機(Motivation)

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

模式定義

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

 

策略模式的本質是使用多態實現程序的擴充性。這里舉一個不使用策略模式和使用策略模式的例子。

模式舉例

我們知道大型的跨國公司在面對不同國家的不同稅法要求需要有不同的策略來應對,假如該公司之前只有中國、美國和德國的稅收應對策略,現在由於要在法國開分公司,因為要實現對法國稅收的支持。如果我們不使用策略模式,很有可能使用下面的代碼,增加支持法國的業務,那么就在枚舉類型上加上法國,if-else上增加法國相關的代碼,但這樣設計其實有很大的不妥,違背了設計模式中對擴展開放,對修改封閉的原則。

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

        //....
     }
    
};

並且這樣修改也並不算是一種真正的復用,真正的復用是編譯單位二進制的復用,就像在適應法國稅務業務上,我們最好不需要因為修改源程序而重新編譯整個程序,而是通過打升級補丁方式支持新業務。這里我們使用策略模式重新一下代碼:

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); //多態調用
        //...
    }
    
};

可以看到我們使用策略模式重構后只需要派生一個新的子類即可,通過多態調用真正實現復用。

我們知道業務場景的變化可能是非常迅速的,使用if-else要特別小心,這時候要考慮能否使用策略模式設計。

在不使用策略模式的上例中,其實還有對內存資源占用過大的缺點,比如只是使用美國的業務,但卻要將整個判斷代碼存入內存中,判斷代碼中有所有的應對策略,但只需要其中的一種,無疑是一種浪費。

要點總結

  • Strategy及其子類為組件提供了一系列可重用的算法,從而可以使得類型在運行時方便地根據需要在各個算法之間進行切換。
  • Strategy模式提供了用條件判斷語句以外的另一種選擇,消除條件判斷語句,就是在解耦合。含有許多條件判斷語句的代碼通常都需要Strategy模式。
  • 如果Strategy對象沒有實例變量,那么各個上下文可以共享同一個Strategy對象,從而節省對象開銷。

基本代碼

#include <iostream>
using namespace std;

class Strategy { // 抽象算法類
public:
    virtual void AlgorithmInterface() = 0; // 算法方法
    virtual ~Strategy(){}
};

class ConcreteStrategyA : public Strategy { // 具體算法A
public:
    void AlgorithmInterface() {
        cout << "ConcreteStrategyA" << endl;
    }
};

class ConcreteStrategyB : public Strategy { // 具體算法B
public:
    void AlgorithmInterface() {
        cout << "ConcreteStrategyB" << endl;
    }
};

class Context { // 上下文
private:
    Strategy* strategy;
public:
    Context(Strategy* s) { strategy = s; }
    void ContextInterface() { // 上下文接口
        strategy->AlgorithmInterface();
    }
};

int main() {
    Strategy* s = new ConcreteStrategyA();
    Context* c = new Context(s);
    c->ContextInterface();  // ConcreteStrategyA
    delete s;
    delete c;
    return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM