架构设计听起来很高大上,今天我们来简单看一下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 就是用的这个设计模式,理解了这个过程我们就学会了一种简单的架构设计了,这样无论是开发自己的框架,还是在别人的框架上开发,都会变得轻松了不少。