策略模式

233 阅读5分钟

前言

最近进入一家GPU开发的公司实习,目前的实习内容是了解GPU的工作原理、图形学基础、以及图形接口API。目前笔者的主要工作是学习OpenGL接口。这一个月以来,经过阅读书籍和资料,对图形学已经有了初步的了解,目前的工作是阅读AMD公司实现的OpenGL的源码,但是随着源码阅读的推进,笔者发现阅读大型程序的源码时,如果没有设计模式的经验,导致大量的源码无法看懂,其中的一些专有名词也无从知晓,其中的类设计让人一头雾水。综上,笔者决定学习设计模式。其中有些错误,请多多指教。

什么是设计模式?

设计模式。在C++20设计模式这本书中对于这个概念的定义:设计模式是专家总结出来的一套被反复使用的、众所周知的、经过分类编目的代码设计经验,是为了解决某类重复出现的问题而提出的一套成功或有效的解决方案。我认为,无论在哪一个行业,前辈的经验都是无比宝贵的。

策略模式

为什么要先学习策略模式,是因为我们在初学C++时,这种设计思想就体现在标准库中,比如我们指定特定的排序算法时,其实就是在指定的排序策略,我们的策略思想,就是为整个算法提供思想。 这里我引用EfficLib的例子。假设你是负责不同类型的汽车和其对应的制动系统关系的建模。最初,工厂只有两种类型的汽车:轿车与SUV。并且其使用的制动系统分别是“简单制动系统”与“ABS"制动系统。

image.png 也许你会这样建立关系:

//汽车基类
struct Car{
   virtual void brake() = 0;
   virtual ~Car();
}

//轿车类
struct Sedan : Car{
    void brake() override{
      println("Simple brake applied ");
    }
}

//SUV类
struct SUV : Car{
    void brake() override{
      println("SUV brake appiled");
    }
}

代码看着非常简洁,并且一切看起来都很美好。 如果,轿车的制动系统升级为ABS,我们只需要在Sedan类中修改brake()函数即可。后来,工厂又研发了一种新车型名为race的赛车车型搭载简单制动系统。此时,我们需要在再从基类派生一个子类Race并重新实现:

//赛车类
struct Race : Car{
    void brake() override{
      println("Simple brake appiled");
    }
}

在以上修改和添加的过程中,我们可以明显发现以下问题:

  1. barke接口的重复实现(Implementation Dumpliaction)
  2. 子类的实现会被频繁修改,比如制动方式的改变,会重写整个方法

为了解决上面的问题,就会引入今天的主角:策略模式(Strategy Parrten) 我们来一步步解决上面的问题: 首先我们能想到的解决方法是:从子类中提取不同的brake()方法的接口实现,并把他们包装成独立的可重用的代码单元。为了让这些单元有一致的接口,还需一个公共的基类:Brake

image.png

基类的逻辑非常简单如下:

//制动类基类
struct Brake{
    virtual void brake() = 0;
    virtual ~Brake();
}

//ABS制动方法类
struct ABSBrakeBrake{
    void brake() override{
        println("ABS brake applied");
    } ;
}

//简单制动方法类
struct SampleBrakeBrake{
    void brake() override{
        println("Sample brake applied");
    } ;
}

将制动方式的实现写在其他类中,Car类中需要添加一个指针,指向Brake基类。这个指针可以由Car的子类来初始化,并指向某个Brake子类。通过这种方式任何一个Car子类就可以通过该指针引用一个特定的制动系统的实现。

image.png

让我们看看对应的代码如何:

//汽车基类
struct Car{
   std::shared_ptr<Brake>brakeSystem; //首先类成员声明一个指向Brake类的智能指针brakeSystem
   Car(std::shared_ptr<Brake> brake):brakeSystem(brake){}; //构造函数中,将指针传递
}

//轿车类
struct Sedan : Car{
  Sedan(std:shared_ptr<Brake> brake):Car(brake){};
}

//SUV类
struct SUV : Car{
   SUV(std:shared_ptr<Brake> brake):Car(brake){};
}

//赛车类
struct Race : Car{
   Race(std:shared_ptr<Brake> brake):Car(brake){};
}
//对应的main函数中
int main()
{
    Sedan sedan{
        std::shared_ptr<ABSBrake>()
    };
    sedan.brakeSystem->brake();
    return 0;

}

这就是策略模式的思想,将制动系统独立出来封装在单独的类中,而不同的Car子类可以动态的使用。

总结

策略模式允许我们定义通用的算法框架,然后以组件的形式提供框架内部流程的具体实现。该模式由几种不同的实现方式:

  1. 动态策略:维护了指向策略的指针或引用。切换到另一个不同的策略只需要修改指针或引用即可。
  2. 静态策略:在编译时就敲定具体的策略--之后就没机会再修改了(枚举策略:使用枚举来定义不同的策略,然后在代码中通过枚举值来选择具体的策略实现。 宏策略:使用宏来定义不同的算法实现,然后通过宏来选择使用哪个算法)

C++的设计原则--开闭原则

solid原则之一--开闭原则指在开发过程中需要考虑到后续功能拓展,尽可能不修改原有的代码,而将新引入的内容进行拓展,也即对修改关闭,对拓展开放。这就需要开发者在设计类时,将可拓展性优先级置于前列。