早绑定——————>晚绑定
对于某一个项目,他有稳定的(不经常修改的)流程和一部分子步骤;同时,他也有一部分经常修改(不稳定)的子步骤,我们希望把程序的主流程封装进库中,而库的使用者只实现不稳定的子步骤,这时我们就需要模板方法模式。
!!!使用模板方法模式可以显著地增加程序员头发的数量。
复杂的说,现代的软件专业分工之后第一个结果是“框架(库)和应用程序之间的划分”,“组件协作型模式”通过晚绑定,来实现框架和应用程序之间的松耦合,是二者之间协作是常用的模式。而模板方法模式就是把程序结构中稳定的流程放到框架中,而不稳定的子步骤(需求经常更改的步骤)放到应用程序中,通过早绑定->晚绑定的变化,以此实现框架和应用程序之间的协作。
光凭口头表达确实抽象,请看下面的例子
假设有很多个程序有=需要很多个人开发,一个人开发一个程序库,很多个人开发不同的应用程序,他们要完成一些不同的工作,但这些工作的流程是一样的。
为了减少代码量,和增加程序员的头发数量,我们打算让库开发人员来实现稳定的步骤(不变化的),让不同的程序开发人员来各自实现不稳定的步骤(经常变化的)。
这个工作有五个步骤,由于步骤1,3,5是不会变化的,所以步骤1,3,5由库完成;而步骤2,4是经常变化的,所以步骤2,4由应用程序完成。(实例代码有所简化,并没有完全按照c++标准写)
对于程序库的开发人员
//程序库的开发人员
class libraryWork{
public:
void step1()
{
//步骤一
}
void step3()
{
//步骤三
}
void step5()
{
//步骤五
}
};
对于应用程序开发人员
//应用程序开发人员
class appWork{
public:
void step2()
{
//步骤二
}
void step4()
{
//步骤四
}
};
int main()
{
libraryWork lib;
appWork app;
lib.step1();
app.step2();
lib.step3();
app.step4();
lib.step5();
return 0;
}
这样虽然整个应用程序的流程(步骤1,2,3,4,5),是不变的,但是我们不得不让程序开发人员来实现这个稳定的流程。由于稳定的步骤只需要写一次,但是实际上我们却写了很多次,这样既增加了代码量,还会显著的减少程序员头发的数量。
//程序库的开发人员
class libraryWork
{
public:
void Run() {
step1();
step2();
step3();
step4();
step5();
}
virtual ~libraryWork(){ };//基类的析构函数必须是虚函数
protected:
void step1()
{
//步骤一
}
void step3()
{
//步骤三
}
void step5()
{
//步骤五
}
virtual void step2() = 0;
virtual void step4() = 0;
};
//应用程序开发人员
class appWork : public libraryWork
{
protected:
virtual void step2()
{
//步骤二(虚函数重载)
}
virtual void step4()
{
//步骤四(虚函数重载)
}
};
int main()
{
libraryWork *plib = new appWork();//plib是一个多态指针
plib->Run();
delete plib;
return 0;
}
。
模板方法模式把主流程放到库里面实现,样例1是应用程序调用库来实现功能,这是早绑定。样例2则是库调用应用程序来实现功能,这是晚绑定,展示了本节的重构关键技法。因为库只需要写一次,而应用程序需要写很多个。把程序主流程放到库里面,可以节约很多代码,使项目结构更为简洁,减少程序员的工作量,从而显著的增加程序员的头发的数量
事实上对于step2和step4这种在编译是就可以确定的函数我们并不建议使用虚函数,或者说,这种编译时就可以确定调用那个函数,我们不建议使用动态的多态,应该使用静态的多态。
因为使用虚函数会造成一定程度上的性能损耗。构造函数必须初始化vptr(虚函数表);虚函数是通过指针间接调用的,所以必须先得到指向虚函数表的指针,然后再获得正确的函数偏移量;内联是在编译时决定的,编译器不可能把运行时才解析的虚函数设置为内联,无法内联虚函数造成的性能损失最大。
某些情况下,在编译期间解析虚函数的调用是可能的,但这是例外情况。由于在编译期间不能确定所调用的函数所属的对象类型,所以大多数虚函数调用都是在运行期间解析的。编译期间无法解析对内联造成了负面影响。由于内联是在编译期间确定的,所以它需要具体函数的信息,但如果在编译期间不能确定将调用哪个函数,就无法使用内联。
评估虚函数的性能损失就是评估无法内联该函数所造成的损失。这种损失的代价并不固定,它取决于函数的复杂程度和调用频率。一种极端情况是频繁调用的简单函数,它们是内联的最大受益者,若无法内联则会造成重大性能损失。另一极端情况是很少调用的复杂函数。
通过对类选择进行硬编码或者将它作为模板参数来传递,可以避免使用动态绑定。
因为函数调用的动态绑定是继承的结果,所以消除动态绑定的一种方法是用基于模板的设计来替代继承。模板把解析的步骤从运行期间提前到编译期间,从这个意义上说,模板提高了性能。而对于我们所关心的编译时间,适当增加也是可以接受的。
下面是样例代码
//程序库的开发人员
template class libraryWork
{
public:
inline void Run() {
step1();
m_child->step2();
step3();
m_child->step4();
step5();
}
libraryWork(){
m_child = static_cast(this);
}
~libraryWork() {};//基类的析构函数必须是虚函数
protected:
//预设步骤
inline void step1(){
//步骤一
cout << "libraryWork 步骤一" << endl;
}
inline void step3(){
//步骤三
cout << "libraryWork 步骤三" << endl;
}
inline void step5(){
//步骤五
cout << "libraryWork 步骤五" << endl;
}
//app应该重写的步骤
inline void step2(){
cout << "libraryWork 步骤二" << endl;
}
inline void step4(){
cout << "libraryWork 步骤四" << endl;
}
private:
T *m_child;
};
//应用程序开发人员
class appWork : public libraryWork
{
public:
inline void step2()
{
//步骤二(静态重载)
cout << "appWork 步骤二" << endl;
}
inline void step4()
{
//步骤四(静态重载)
cout << "appWork 步骤四" << endl;
}
};
int main()
{
appWork *ourWork = new appWork();//ourWork是一个静态多态指针
ourWork->Run();
delete ourWork;
return 0;
}
```
#ifndef _MYSCENE_H_
#define _MYSCENE_H_
#include "cocos2d.h"//最重要的包。
#include "ui\CocosGUI.h"//ui相关。
USING_NS_CC;//最重要的命名空间。
class MyScene : public Layer
{//继承Layer,用于构建组成MyScene的图层。
public:
MyScene();
~MyScene();
virtual bool init();//成员变量初始化方法。
void update(float dt);//用于对成员变量进行操控的方法。
CREATE_FUNC(MyScene);//使用宏方法构建场景。
static Scene* createScene();//定义用于方便全局使用的构建场景方法。
private:
Sprite* mySprite;//组成场景的精灵,这里定义为私有成员方便update()方法对其调用。
};
#endif
```
其实场景的渲染流程跟我们刚刚的样例一样,被封装在scene类的基类(Layer类)里面了。这就是模板方法模式在实际项目中的一些应用