【重走编程路】设计模式概述(八) -- 策略模式、模板方法模式

文章目录

  • 前言
  • 13. 策略模式(Strategy)
    • 定义
    • 解决方案
    • 应用场景
    • 优缺点
  • 14. 模板方法模式(Template Method)
    • 问题
    • 解决方案
    • 应用场景
    • 优缺点


前言

行为型模式关注对象之间的交互以及如何分配职责,提供了一种定义对象之间的行为和职责的最佳方式。本章介绍创建型模式中的策略模式和模板方法模式。


13. 策略模式(Strategy)

定义

策略模式定义了一系列算法,并将每一种算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。策略模式属于对象行为型模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象管理。

解决方案

策略模式提供了一种定义一系列算法的方法,将这些算法封装成独立的策略类,并使它们可以相互替换。在客户端中,创建一个上下文(Context)对象,该对象包含一个对策略类的引用,通过该引用调用相应的策略方法。这样,客户端可以在运行时选择不同的策略,而不需要修改上下文类。
策略模式主要围绕以下几个角色来构建:

  1. 策略接口(Strategy Interface): 定义所有支持的算法的公共接口。
  2. 具体策略类(Concrete Strategy): 实现了策略接口的类,封装了具体的算法或行为。
  3. 上下文(Context): 接受客户的请求,随后把请求委托给某一个具体策略类。上下文会维护对策略对象的引用,并可在运行时动态地改变策略。
#include   
#include   

// 策略接口  
class DiscountStrategy {
public:
    virtual double CalculateDiscount(double originalPrice) = 0;
    virtual ~DiscountStrategy() {}
};

// 具体策略类:无折扣  
class NoDiscountStrategy : public DiscountStrategy {
public:
    double CalculateDiscount(double originalPrice) override {
        return originalPrice;
    }
};

// 具体策略类:普通用户折扣  
class NormalDiscountStrategy : public DiscountStrategy {
public:
    double CalculateDiscount(double originalPrice) override {
        return originalPrice * 0.9; // 10% 折扣  
    }
};

// 具体策略类:高级用户折扣  
class SeniorDiscountStrategy : public DiscountStrategy {
public:
    double CalculateDiscount(double originalPrice) override {
        return originalPrice * 0.8; // 20% 折扣  
    }
};

// 上下文  
class ShoppingCart {
private:
    std::unique_ptr<DiscountStrategy> discountStrategy;

public:
    ShoppingCart(std::unique_ptr<DiscountStrategy> strategy)
        : discountStrategy(std::move(strategy)) {}

    double GetTotalCost(double originalPrice) {
        return discountStrategy->CalculateDiscount(originalPrice);
    }

    void SetDiscountStrategy(std::unique_ptr<DiscountStrategy> strategy) {
        discountStrategy = std::move(strategy);
    }
};

// 主函数  
int main() {
    ShoppingCart cart(std::make_unique<NormalDiscountStrategy>());
    std::cout << "Normal User Total Cost: $" << cart.GetTotalCost(100.0) << std::endl;
    
cart.SetDiscountStrategy(std::make_unique<SeniorDiscountStrategy>());
    std::cout << "Senior User Total Cost: $" << cart.GetTotalCost(100.0) << std::endl;
    
cart.SetDiscountStrategy(std::make_unique<NoDiscountStrategy>());
    std::cout << "No Discount Total Cost: $" << cart.GetTotalCost(100.0) << std::endl;

    return 0;
}

应用场景

  1. 算法自由切换: 在多种算法间自由切换,且算法改变不会影响到使用算法的客户。
  2. 避免使用多重条件判断: 如果在一个系统中有很多地方都使用了类似的多重条件(if-else或switch-case)语句来选择不同的算法,使用策略模式可以避免这种情况。

优缺点

优点:

  • 算法独立:算法可以独立于使用它们的客户而变化。
  • 易于扩展:增加新的策略类很容易,无需修改已有代码,符合开闭原则。
  • 避免多重条件判断:使用策略模式可以避免使用难以维护的多重条件语句。

缺点:

  • 策略类数量增加:当策略类数量较多时,系统的复杂度会增加。
  • 客户端必须了解所有策略:客户端需要知道所有的策略,才能决定使用哪一个策略。

14. 模板方法模式(Template Method)

问题

在软件开发中,经常会有一些算法或流程在多个类中都有相似的结构,但某些步骤的具体实现可能因类而异。如果将这些算法或流程完全硬编码在每个类中,不仅会导致代码重复,而且难以维护和扩展。

解决方案

模板方法模式通过定义一个算法的骨架,将不变的部分封装在父类中,而将可变的部分延迟到子类中实现。这使得子类可以根据需要重写特定的步骤,而核心算法结构保持不变。父类中的模板方法调用这些步骤,确保算法的整体流程一致。
模板方法模式通过以下方式解决问题:

  1. 定义抽象类:首先定义一个抽象类,它包含了算法或流程的骨架,即模板方法。模板方法是一个具体的方法,它通常调用了一组抽象方法(也称为钩子方法)。
  2. 实现抽象方法:在子类中实现这些抽象方法,以提供算法或流程中可变部分的具体实现。
  3. 调用模板方法:通过调用模板方法,可以执行算法或流程的完整过程,而不需要在客户端代码中显式地调用各个步骤。
#include   
  
// 抽象类,定义了算法的骨架  
class Game {  
public:  
    // 模板方法,定义了游戏流程的骨架  
    void Play() {  
        Initialize();  
        StartGame();  
        EndGame();  
    }  
 
protected:  
    // 抽象方法,子类必须实现  
    virtual void Initialize() = 0;  
    virtual void StartGame() = 0;  
    virtual void EndGame() = 0;  
};  
  
// 具体类,实现了抽象方法  
class Football : public Game {  
protected:  
    void Initialize() override {  
        std::cout << "Football game is initializing..." << std::endl;  
    }  
    void StartGame() override {  
        std::cout << "Football game has started!" << std::endl;  
        // 假设这里有一些游戏逻辑...  
    }  
    void EndGame() override {  
        std::cout << "Football game has ended." << std::endl;  
    }  
};  
  
// 另一个具体类  
class Basketball : public Game {  
protected:  
    void Initialize() override {  
        std::cout << "Basketball game is initializing..." << std::endl;  
    }  
  
    void StartGame() override {  
        std::cout << "Basketball game has started!" << std::endl;  
        // 假设这里有一些游戏逻辑...  
    }  
  
    void EndGame() override {  
        std::cout << "Basketball game has ended." << std::endl;  
    }  
};  

int main() { 
    Game* game1 = new Football(); 
    Game* game2 = new Basketball(); 
  
    game1->Play(); // 执行足球游戏
    game2->Play(); // 执行篮球游戏
  
    delete game1; 
    delete game2; 
    return 0;  
}

p.s. 这不就是父类子类继承嘛

应用场景

  1. 算法框架:当多个类具有相似的算法结构,但某些步骤的具体实现不同时。
  2. 框架设计:在设计框架时,可以将不变的部分封装在模板方法中,而将可变的部分留待子类实现。
  3. 复用代码:在需要复用代码但又需要灵活调整某些步骤的实现时。

优缺点

优点:

  • 封装算法骨架:将算法的骨架封装在父类中,使得子类可以专注于实现算法的具体步骤。
  • 提高代码复用性:通过继承父类,子类可以复用父类中定义的算法骨架。
  • 增强扩展性:当需要增加新的算法或流程时,只需新增一个子类并实现相应的抽象方法即可。

缺点:

  • 增加系统复杂性:如果系统中存在大量的模板方法,可能会导致类的继承层次变得复杂。
  • 违反了开闭原则:虽然模板方法模式本身遵循了开闭原则(对扩展开放,对修改关闭),但在某些情况下,如果需要对模板方法中的某些步骤进行修改,可能需要修改父类,从而违反了开闭原则。不过,这种情况可以通过钩子方法来解决。

To be continued.

你可能感兴趣的:(重走编程路,设计模式,策略模式,模板方法模式)