策略模式

策略模式

目的

在开发过程中,常使用 if-else 这样的多个分支来应对程序中的多种情况,每一个分支就是一种情况;随着需求的不断增多,分支将会迅速膨胀,造成代码块的冗长,且很有可能这样的分支远不止一处,这会给代码的维护扩展带来极大困难,策略模式可以用来解决上述问题;

概念

策略模式概念:定义一系列算法,将各算法进行封装,使得它们可以相互替换;

策略模式让算法独立于使用它的用户而变化,当有新的算法时,代码进行扩展即可,无需过多变动;

一般在项目中,若发现多个 if-else 且预计还会在此增加多个 if-else 分支,那很有可能需要使用策略模式;

策略模式将算法行为封装为对象,以便动态选择,而不是使用显式 if-else;

示例

重构代码对比策略模式

分别列举未使用策略模式的原始代码与使用策略模式的重构后代码进行对比,两者代码分述如下;

原始代码:

enum class GridGenMethod {    
    TransfiniteInterp,    
    EllipticEqs,
    AdvancingFrontTech,
    DelaunayTri
};

// ...


// 用户代码
void genGrid(const GridGenMethod method) 
{   
    std::cout << "gen grid start." << std::endl; 
    
    if (method == GridGenMethod::TransfiniteInterp) {        
        std::cout << "TransfiniteInterp Method : " << std::endl;  
        // ...
    } else if (method == GridGenMethod::EllipticEqs) {        
        std::cout << "EllipticEqs Method : " << std::endl;  
        // ...   
    } else if (method == GridGenMethod::AdvancingFrontTech) {
        std::cout << "AdvancingFrontTech Method : " << std::endl;  
        // ...
    } else if (method == GridGenMethod::DelaunayTri) {
        std::cout << "DelaunayTri Method : " << std::endl;  
        // ...
    }
    
    // ...
    
    std::cout << "gen grid finish." << std::endl;
}

int main()
{
    GridGenMethod method = GridGenMethod::EllipticEqs;
    genGrid(method);
    
    // ...
    return 0;
}

使用策略模式重构后代码:

// 库代码
class GridGenerator {   
public:
    GridGenerator() = default;
    GridGenerator(const GridGenerator& rhs) = default;
    GridGenerator& operator=(const GridGenerator& rhs) = default;
    GridGenerator(GridGenerator&& rhs) = default;
    GridGenerator& operator=(GridGenerator&& rhs) = default;
    virtual ~GridGenerator() {
        // ...
    }
    virtual void genGrid() { 
        std::cout << "virtual genGrid" << std::endl; 
    }
private:
    //...
};

class TransfiniteInterpGenerator : public GridGenerator {    
    virtual void genGrid() override { 
        std::cout << "TransfiniteInterp Method : " << std::endl; 
        // ...
    }
};

class EllipticEqsGenerator : public GridGenerator {    
    virtual void genGrid() override { 
        std::cout << "EllipticEqs Method : " << std::endl; 
        // ...
    }
};

class AdvancingFrontTechGenerator : public GridGenerator {    
    virtual void genGrid() override { 
        std::cout << "AdvancingFrontTech Method : " << std::endl; 
        // ...
    }
};

class DelaunayTriGenerator : public GridGenerator {    
    virtual void genGrid() override { 
        std::cout << "DelaunayTri Method : " << std::endl; 
        // ...
    }
};

// 若后续有新的方法,还可以在此添加新方法的代码
// ...

// 用户代码 : 相比于原始代码,精简了很多
int main() 
{    
    std::unique_ptr<GridGenerator> gridGen = std::make_unique<EllipticEqsGenerator>();
    gridGen->genGrid();
    // ...
    
    return 0;
}

每增加一种方法,就增加一个继承基类的子类,重写 genGrid() 函数进行代码扩展即可;

观察上述代码,模板方法的代码和策略模式看起来很类似,但两者有很大区别:

  • 模板方法侧重的是将整体框架流程提供给代码使用者,代码使用者重新实现某一些具体行为即可;

  • 策略模式侧重的是将算法库代码分别封装,使得它们可以相互替换,代码使用者选择其一即可;

当然,两种模式均使用了C++继承与重写机制来实现各自模式;

使用函数对象

还可以使用C++中函数对象来实现策略模式,与C++继承及重写类似,但是更直观易懂,其亦被称为表驱动法,示例代码如下:

// 库代码
class TransfiniteInterpGenerator {
public:
    void operator()() { 
        std::cout << "TransfiniteInterp Method : " << std::endl; 
        // ...
    }
    // ...
};

class EllipticEqsGenerator {
public:
    void operator()() { 
        std::cout << "EllipticEqs Method : " << std::endl; 
        // ...
    }
    // ...
};

class AdvancingFrontTechGenerator {
public:
    void operator()() { 
        std::cout << "AdvancingFrontTech Method : " << std::endl; 
        // ...
    }
    // ...
};

class DelaunayTriGenerator { 
public:
    void operator()() { 
        std::cout << "DelaunayTri Method : " << std::endl; 
        // ...
    }
    // ...
};

// 若后续有新的方法,还可以在此添加新方法的代码
// ...

class GridGenerator {
public:
    enum class GridGenMethod {    
        TransfiniteInterp,    
        EllipticEqs,
        AdvancingFrontTech,
        DelaunayTri
    };
    static std::function<void(void)> genGrid(const GridGenMethod method) {
        return s_methodFunc.at(method);
    }
private:
    static map<GridGenMethod, std::function<void(void)>> s_methodFunc = {
        {GridGenMethod::TransfiniteInterp   , TransfiniteInterpGenerator()},
        {GridGenMethod::EllipticEqs         , EllipticEqsGenerator()},
        {GridGenMethod::AdvancingFrontTech  , AdvancingFrontTechGenerator()},
        {GridGenMethod::DelaunayTriGenerator, DelaunayTriGenerator()}
    };  
};


// 用户代码
void main() 
{  
    GridGenerator::GridGenMethod method = GridGenerator::GridGenMethod::EllipticEqs;
    std::function<void(void)> gridGen = GridGenerator::genGrid(method);
    gridGen();
    // ...
    
    return 0;
} 

参考

本文参考 同 本博客前述<面向对象设计>一文中所参考项;


本文作者: 王同学