架构设计?Template Method?懂点继承就能理解

503 阅读3分钟

架构设计听起来很高大上,今天我们来简单看一下Template Method这种设计模式。

首先我们回顾一下继承,我们需要特别注意的是,对于类的继承,其中的函数继承的是调用权。

子类要不要重新定义呢?

那么就有了下面的三种函数。

  • pure virtual:

    • 你希望派生类一定要重新定义它,对它没有默认定义
    • 事实上纯虚函数可以有定义,但一般不讨论,暂时可以认为它是没有定义的。
    • 父类不清楚怎么做,比如绘制,需要到具体的形状进行具体的draw。
  • virtual:

    • 你希望派生类子类重新定义它,且已有默认定义
    • 如果有精细的想法,允许子类重新定义,如果没有就用默认的
  • non-virtual:

    • 你不希望函数被override

实例分析

一起先来看一个很很简单的关于形状的例子,由于 Shape 类不知道具体的形状如何绘制,所以定义了一个纯虚函数,希望子类进行具体的重写。对于 error 这种错误提示,往往在基类就可以给出一个通用的错误提示,子类如果有更具体的错误提示那么就进行 override,如果没有就使用积累定义的。

class Shape
{
public:
    virtual void draw() const = 0;//pure virtual
    virtual void error( const std::string& nsg);//virtual:
    int objectID() const;//non-virtual
    ...
}
class Rectangle:public Shape{...};
class Elipse:public Shape{...};

在实际开发的过程中有什么价值呢?

  • 找到商机卖软件
  • 买到软件会重写

对于我们实际编程中的价值就是,我们买来了第三方的一些 SDK;比如下面这个打开文件的这个SDK,开发的厂商已经写好了打开文件的一些常规流程,选择文件名称、查找文件位置等等,但是对于都内容,需要根据我们实际的内容进行处理,因此 Serialize() 读内容这部分功能,就需要派生类进行自定义的重写,所以厂商就可以把 Serialize() 定义成纯虚函数 virtual void Serialize() = 0;,以便让购买的用户既能享有基础的功能,重写关键性的解析步骤。

// 文件操作基类
class CDocument
{
public:
    void OnFileOpen();
    void Serialize(); // 读内容
}

void CDocument::OnFileOpen()
{
    ... // 选择文件名称
    ... // 查找文件位置
    ...
    void Serialize(); // 读内容
    ...
}

我们买来,写我们的子类,

class MyDocument : public CDocument
{
    virtual void Serialize(){...};
}

接下来,我们就可以通过子类对象调用父类的函数

int main()
{
    MyDocument myDoc;
    myDoc.OnFileOpen();
}

其中 myDoc.OnFileOpen(); 的调用路径是
1️⃣this pointer 就是 &myDoc,被传进来, CDocument::OnFileOpen(&myDoc)
2️⃣执行CDocument::OnFileOpen中的非虚函数
... // 选择文件名称 ... // 查找文件位置 ...
3️⃣执行CDocument::OnFileOpen中的 Serialize,发现是虚函数
4️⃣找到 “执行者” &myDoc
5️⃣进入 “执行者” 的类中重写的 Serialize
class MyDocument : public CDocument { virtual void Serialize(){...}; }
6️⃣执行完毕之后,再回到 CDocument::OnFileOpen() 中执行剩余的其他非虚函数
7️⃣最后回到主函数

把关键动作,延缓到子类重写,这个函数的做法叫 Template Method,大名鼎鼎的 MFC 就是用的这个设计模式,理解了这个过程我们就学会了一种简单的架构设计了,这样无论是开发自己的框架,还是在别人的框架上开发,都会变得轻松了不少。