模板方法模式(c++)(组件协作型模式)

设计模式(一)“组件协作型模式”之模板方法模式(c++)

 本节的重构关键技法:(看不懂直接看简单例子)

 早绑定——————>晚绑定


为什么要使用模板方法模式?(看不懂直接看简单例子)

对于某一个项目,他有稳定的(不经常修改的)流程和一部分子步骤;同时,他也有一部分经常修改(不稳定)的子步骤,我们希望把程序的主流程封装进库中,而库的使用者只实现不稳定的子步骤,这时我们就需要模板方法模式。
!!!使用模板方法模式可以显著地增加程序员头发的数量。


模板方法模式的作用(看不懂直接看简单例子)


复杂的说,现代的软件专业分工之后第一个结果是“框架(库)和应用程序之间的划分”,“组件协作型模式”通过晚绑定,来实现框架和应用程序之间的松耦合,是二者之间协作是常用的模式。而模板方法模式就是把程序结构中稳定的流程放到框架中,而不稳定的子步骤(需求经常更改的步骤)放到应用程序中,通过早绑定->晚绑定的变化,以此实现框架和应用程序之间的协作。

    简单的说,早绑定就是在应用程序中调用库,而晚绑定就是在库中调用应用程序,
    模板方法模式就是把主流程放在库里,库的使用者只需要实现子步骤就可以了。


光凭口头表达确实抽象,请看下面的例子

 

简单例子


假设有很多个程序有=需要很多个人开发,一个人开发一个程序库,很多个人开发不同的应用程序,他们要完成一些不同的工作,但这些工作的流程是一样的。
为了减少代码量,和增加程序员的头发数量,我们打算让库开发人员来实现稳定的步骤(不变化的),让不同的程序开发人员来各自实现不稳定的步骤(经常变化的)。
这个工作有五个步骤,由于步骤1,3,5是不会变化的,所以步骤1,3,5由库完成;而步骤2,4是经常变化的,所以步骤2,4由应用程序完成。(实例代码有所简化,并没有完全按照c++标准写)

样例1,如果不用模板方法模式,代码将会是这样的

对于程序库的开发人员

 

//程序库的开发人员
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),是不变的,但是我们不得不让程序开发人员来实现这个稳定的流程。由于稳定的步骤只需要写一次,但是实际上我们却写了很多次,这样既增加了代码量,还会显著的减少程序员头发的数量。

样例2,如果使用模板方法模式,代码将会是这样的

//程序库的开发人员
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 T> class libraryWork
{
public:
	inline void Run() {
		step1();
		m_child->step2();
		step3();
		m_child->step4();
		step5();
	}

	libraryWork(){
		m_child = static_cast<T*>(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<appWork>
{
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;
}

 

实际例子
实际项目中,如cocos2dx的场景设计就使用了模板方法模式,代码如下

```

#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类)里面了。这就是模板方法模式在实际项目中的一些应用

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值