Featured image of post 模板方法和策略模式

模板方法和策略模式

介绍了模板方法模式和策略模式

引言

模板方法模式策略模式都是行为设计模式,它们的目标都是通过封装算法来提高代码的灵活性和可维护性。然而,它们的实现方式和适用场景有所不同。下面就会介绍这两种设计模式

模板方法模式

模板方法的核心思想就是,将操作步骤固定(相当于给你了一个模板),而实现这些步骤的方法是可以替换的。

  • 举个例子,班主任想要对班级同学的成绩进行统计并进行排序,学校规定使用了一种软件,这种软件需要人为完成一些步骤才能实现统计。
    • 将成绩登记到电脑上
    • 对数据进行排序
    • 公布成绩(不要这样做!!!不要打击学生的自尊心)
  • 我们必须严格按照学校的要求来做,但是我们可以通过不同的方法来完成学校的要求
    • 对于登记到电脑上
      • 这好像只能将成绩一个一个登记到电脑上,没有其他办法了。
    • 对数据进行排序
      • 我们可以使用冒泡排序
      • 也可以使用快速排序
    • 最后公布成绩
      • 我们可以只公布分数而不公布姓名
      • 我们也可以将分数和姓名一起公布(要被学生骂死了)

言归正传,这种情况下学校使用的软件相当于一个模板方法,但是有些步骤我们可以选择不同的操作方式。这就是模板方法的思想。

代码展现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Base {
public:
    Base() = default;
    virtual ~Base() = default;
    void DoWork() {
        Step1();Step2();Step3();
        Step4();        //钩子操作,空操作,由子类选择是否可以扩展这个操作
    }

private:
    static void Step1() {std::cout << "step1" << std::endl;}
    static void Step3() {std::cout << "step3" << std::endl;}
protected:
    virtual void Step2() = 0;
    virtual void Step4() {}
};

//用户来决定使用哪些方法。
class Derived1 : public Base {
    ~Derived1() override = default;
    void Step2() override {std::cout << "derived1's step2" << std::endl;}
};

class Derived2 : public Base {
    ~Derived2() override = default;
    void Step2() override {std::cout << "derived2's step2" << std::endl;}
    void Step4() override {std::cout << "extend the hook operator" << std::endl;}
};

int main() {
    Base* p1 = new Derived1();
    Base* p2 = new Derived2();
    
    p1->DoWork();
    std::cout << "------------------" << std::endl;
    p2->DoWork();
    
    delete p1;
    delete p2;
    return 0;
}
  • 上面的模板函数给出了四个step,其中step2已经被模板方法写死了,你只能这么做(非虚函数),但是有些step方法没有实现(纯虚函数),给了你自由度选择自己的操作方式。
  • 我们可以看到有一个step4钩子操作,这个构造操作在基类里面通常是个空操作,用来说明在这个地方你可以选择进行一些操作来进行优化,但是如果你优化也ok,这取决与你自己的意愿。
  • 这样用户就可以定义派生类来继承基类,重写方法来决定一要用什么具体的步骤,你也可以重写上面step4操作进行操作上的一些扩展。

策略模式

策略模式提供了一种切换算法的思想,对于一个功能,我们可以切换里面的算法来实现不同的功能。对于策略模式,程序员可以先实现不同的策略,等到需要切换策略的时候 ,换一种策略即可。

  • 场景:电商平台的折扣策略

    假设你在一家电商公司工作,负责实现一个购物车的结算功能。购物车需要支持多种折扣策略,例如:

    1. 无折扣:原价结算。
    2. 固定折扣:比如满100减10。
    3. 百分比折扣:比如打8折。
    4. 会员专属折扣:根据会员等级提供不同的折扣。

    产品经理可能会随时调整折扣策略,或者根据促销活动动态切换折扣方式。如果每次需求变更都修改结算逻辑,代码会变得难以维护。这时,策略模式就可以派上用场了!


    策略模式的核心思想

    策略模式的核心是:

    • 将不同的算法(或行为)封装成独立的类,并使它们可以互相替换。
    • 通过组合而不是继承来实现算法的动态切换。

    用策略模式实现折扣功能

    • 首先我们实现一个打折的接口,再商品进行结算的时候,直接调用这些接口
    • 我们可以实现不同的打折方案,在不同的时候,切换打折方案
    • 这就是策略模式的思想

代码展现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// strategy
class CalcStrategy {
public:
    virtual ~CalcStrategy() = default;
    virtual int calc(int a, int b) = 0;
};

//用户拓展
class AddStrategy final : public CalcStrategy {
    int calc(const int a, const int b) override { return a + b;}
};

class SubStrategy final : public CalcStrategy {
    int calc(const int a, const int b) override { return a - b;}
};

class MulStrategy final : public CalcStrategy {
    int calc(const int a, const int b) override { return a * b;}
};

class DivStrategy final : public CalcStrategy {
    int calc(const int a, const int b) override { return a / b;}
};

//factory
class Factory {
public:
    virtual ~Factory() = default;
    virtual CalcStrategy* NewStrategy() =0;
};

//用户拓展工厂方法
class AddFactory final : public Factory {
    CalcStrategy* NewStrategy() override {return new AddStrategy;}
};

class SubFactory final : public Factory {
    CalcStrategy* NewStrategy() override {return new SubStrategy;}
};

class MulFactory final : public Factory {
    CalcStrategy* NewStrategy() override {return new MulStrategy;}
};

class DivFactory final : public Factory {
    CalcStrategy* NewStrategy() override {return new DivStrategy;}
}; //end of factory

class Calculator {
public:
    explicit Calculator(Factory* strategy_factory) {strategy_ = strategy_factory->NewStrategy();}

    int doCalc(const int a, const int b) const { return strategy_->calc(a, b);}
    ~Calculator() { delete strategy_;}

private:
    //对于策略,采用的是组合的方式来进行,可以切换不通的策略
    CalcStrategy* strategy_;
};      //end of strategy

int main() {
    Factory* add = new AddFactory();
    Factory* sub = new SubFactory();
    Factory* mul = new MulFactory();
    Factory* div = new DivFactory();

    const Calculator calculator1(add);
    std::cout << calculator1.doCalc(1,1) << std::endl;;

    const Calculator calculator2(sub);
    std::cout << calculator2.doCalc(1,1) << std::endl;;

    const Calculator calculator3(mul);
    std::cout << calculator3.doCalc(1,1) << std::endl;;

    const Calculator calculator4(div);
    std::cout << calculator4.doCalc(1,1) << std::endl;;

    delete factory1;
    delete factory2;
    delete factory3;
    delete factory4;
}
  • 可以看出,我们有一个Calculator类用来实现一个计算的功能。
    • Calculater 里面组合了一个Strategy类(可以认为功能和实现功能的策略是紧密联系的),我们可以传入不同的策略类来切换策略实现不同的功能
  • 在本例中,我们可以通过工厂方法创建不同的策略,在创建Strategy对象的时候,传入不同的策略从而达到实现不同的功能的目的。

两种模式的对比

最后,我们再来看一下这两种模式的对比

对比维度 模板方法模式 策略模式
定义 定义算法的框架,子类可以重写部分步骤,但不改变算法结构。 定义一系列算法,封装每个算法,使它们可以互换。
核心思想 将算法的通用部分放在父类中,具体实现延迟到子类。 将算法的选择与使用分离,客户端可以动态选择不同的策略。
实现方式 基于继承,父类定义算法框架,子类实现具体步骤。 基于组合,通过接口或抽象类定义策略,具体策略由不同类实现。
灵活性 较低,算法的结构在父类中固定,子类只能改变部分步骤。 较高,客户端可以动态切换策略,算法可以完全替换。