这里承接上一篇文章,继续记录关于继承的那些事儿...

NVI(non-Virtual Interface)和strategy模式
  NVI模式和strategy模式是两种不同的方法,可以用来替代virtual函数的方法。下面就一个具体任务(随便杜撰的哈)来阐述这三种方法:
  任务(胡诌的):在设计游戏时,通常都会有非玩家控制角色(NPC)的野怪或者boss等。某个时刻,用户想查看野怪或者boss的剩余生命值,以此来确定自己的攻击策略,所以,需要在设计野怪或者boss对应的类时,提供一个函数接口,该函数的功能是计算当前野怪或boss的剩余生命值,并返回之;但是,游戏场景中存在多个用户(角色),他们在某一时刻都想查看野怪或boss的剩余生命值,现在的要求是想让他们互斥的访问,即需要在获取当前剩余生命值时,事先申请一个互斥锁(mutex)来实现互斥的访问;

方法一:基于public继承+virtual函数

 // 定义一个角色扮演无关的抽象基类
class gameNPC{
public:
  //...(省略构造函数和析构函数)
  virtual int calcCurrHealth() const = ;
private:
  //...(省略具体的成员变量)
};
// 定义一个野怪的类,以public的方式继承于gameNPC
class gameMinion : public gameNPC{
public:
  //...(省略构造函数和析构函数)
  // 重写继承而来的纯虚函数
  virtual int calcCurrHealth() const
  {
    //获取互斥锁(mutex)
    Mutex* pMutex = new Mutex(...);
    //...     //计算当前野怪的剩余生命值
    //...     //释放互斥锁
    delete pMutex;
  }
private:
  //...(省略具体的成员变量)
};
// 定义一个boss的类,以public的方式继承于gameNPC
class gameBoss : public gameNPC{
public:
  //...(省略构造函数和析构函数)
  // 重写继承而来的纯虚函数
  virtual int calcCurrHealth() const
  {
    //获取互斥锁(mutex)
    Mutex* pMutex = new Mutex(...);
    //...     //计算当前boss的剩余生命值
    //...     //释放互斥锁
    delete pMutex;
  }
private:
  //...(省略具体的成员变量)
};

  上述方法是很容易想到的,并且也能很好的完成要求的任务。如果还有其他角色扮演无关的对象,依然令其继承于gameNPC类,至于为何要将gameNPC类设计为抽象类,很显然的理由,NPC本身只是一个抽象的概念,是游戏中一类对象的统称,它只能提供calcCurrHealth函数的接口声明,无法提供具体的实现,故纯虚函数是最好的选择。但是该方法存在代码冗余,即每个子类都要完成互斥锁的申请和释放操作。

方法二:NVI(non-Virtal Interface)
  
在摆出具体实现之前,需要具体说明一下该方法。该方法的实现时基于Template method,顾名思义,就是提供一个non-virtual函数接口供用户调用;而对于不同对象的多态性还是用virtual函数去实现,和方法一不同的是,抽离出不相同的部分定义virtual函数,而对于申请mutex和释放mutex对象的共同操作全部放在non-virtual函数接口中,这就是NVI。

 // 定义一个角色扮演无关的抽象基类
class gameNPC{
public:
  //...(省略构造函数和析构函数)
  // 完成任务的non-virtual接口,子类会继承之
  int currHealth() const;
  {
    //获取互斥锁(mutex)
    Mutex* pMutex = new Mutex(...);
    //...     //计算当前boss的剩余生命值
    int healthVal = calcCurrHealth();     //释放互斥锁
    delete pMutex;
    return healthVal ;
  }
private:
  // 抽离出不相同的部分(计算不同对象的剩余生命值)
  virtual int calcCurrHealth() const = ;
  //...(省略具体的成员变量)
};
// 定义一个野怪的类,以public的方式继承于gameNPC
class gameMinion : public gameNPC{
public:
  //...(省略构造函数和析构函数) private:
  // 计算野怪的剩余生命值
  virtual int calcCurrHealth() const
  {
  //...
  }
  //...(省略具体的成员变量)
};
// 定义一个boss的类,以public的方式继承于gameNPC
class gameBoss : public gameNPC{
public:
  //...(省略构造函数和析构函数) private:
  // 计算boss的剩余生命值
  virtual int calcCurrHealth() const
  {
  //...
  }
  //...(省略具体的成员变量)
}; //上面的NVI方法能否完成任务呢?测试一下就知道,测试代码如下:
gameNPC* pNPC = new gameMinion();
printf("%d\n",pNPC->currHealth()); //打印出野怪当前的剩余生命值
pNPC = new gameBoss();
printf("%d\n",pNPC->currHealth()); //打印出boss当前的剩余生命值

方法三:strategy模式
  上述两种方法其实都是基于virtual函数完成的,只是方法二的代码更简洁一些;就功能扩展方面而言,基于strategy模式的方法更好,该设计模式的思想就是抽离出任务,单独为其生成一个接口类。以下是具体实现:

 //将计算不同对象的剩余生命值这个任务抽离出来,定义一个接口类
class calcHealthInterface{
public:
  //...
  virtual int calcHealth() const = ;
};
class calcHealthMinion : public calcHealthInterface{
public:
  //...   //重写calcHealth函数,计算野怪的剩余生命值
  virtual int calcHealth() const
  {
  //...
  }
};
class calcHealthBoss : public calcHealthInterface{
public:
  //...   //重写calcHealth函数,计算boss的剩余生命值
22   virtual int calcHealth() const
  {
  //...
  }
};
// 定义一个角色扮演无关的抽象基类
class gameNPC{
public:
  explicit gameNPC(calcHealthInterface* pFunc):m_pHealthFunc(pFunc)
  { }
  virtual ~gameNPC() { //...}
  // 完成任务的non-virtual接口,子类会继承之
  int currHealth() const;
  {
    //获取互斥锁(mutex)
    Mutex* pMutex = new Mutex(...);
    //...     //计算当前boss的剩余生命值
    int healthVal = m_pHealthFunc->calcHealth();     //释放互斥锁
    delete pMutex;
    return healthVal;
  }
private:
  calcHealthInterface* m_pHealthFunc;
  //...(省略具体的成员变量)
};
// 定义一个野怪的类,以public的方式继承于gameNPC
class gameMinion : public gameNPC{
public:
54   explicit gameMinion(calcHealthInterface* pFunc):gameNPC(pFunc)
  { }
  virtual ~gameMinion() { //... } private:
  //...(省略具体的成员变量)
};
// 定义一个boss的类,以public的方式继承于gameNRP
class gameBoss : public gameNPC{
public:
  explicit gameBoss(calcHealthInterface* pFunc):gameNPC(pFunc)
  { }
  virtual ~gameBoss() { //... } private:
  //...(省略具体的成员变量)
};
//下面是测试代码:
calcHealthInterface* pFuncInterface = new calcHealthMinion();
gameNPC* pNPC = new gameMinion(pFuncInterface);
printf("%d\n",pNPC->currHealth()); //打印出是哪个对象的剩余生命值呢???
pFuncInterface = new calcHealthBoss();
pNPC = new gameBoss(pFuncInterface);
printf("%d\n",pNPC->currHealth()); //打印出是哪个对象的剩余生命值呢???

总结

   看上去方法三较前面两种方法,显得更复杂;哪里能体现出该模式的优点呢?从功能扩展的角度看,如果出现了其他角色扮演无关的对象出现,它们剩余生命值的计算方法和前面的野怪或boss的不同,这时需要做的事情很简单,从calcHealthInterface类派生一个接口类并重写calcHealth函数,然后从gameNPC类派生对应的对象,具体做法和gameMinion及gameBoss一样,OK!!!这样就完成的功能拓展,用户调用时只需要使用基类指针即可,很方便,不是么?

-------------------------------------------
上面提出的任务纯属杜撰不切实际,因为玩游戏时,对于boss的生命值查看不存在写操作,多个用户同时查看没有任何影响,不需要加锁的,这里只是想提供一些额外的共同操作,以此来说明代码的简洁性及可扩展性是如此的重要。
-------------------------------------------

c++学习_2的更多相关文章

  1. 特征点检测学习_2(surf算法)

    依旧转载自作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 特征点检测学习_2(surf算法) 在上篇博客特征点检测学习_1(sift算法) 中 ...

  2. 模式匹配之surf----特征点检测学习_2(surf算法)

    在上篇博客特征点检测学习_1(sift算法) 中简单介绍了经典的sift算法,sift算法比较稳定,检测到的特征点也比较多,其最大的确定是计算复杂度较高.后面有不少学者对其进行了改进,其中比较出名的就 ...

  3. 《windows程序设计》学习_2.2:初识消息,双键的使用

    /* 双键的使用 */ #include <windows.h> LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); int WINAPI ...

  4. 《windows程序设计》学习_2.1:初识消息

    #include <windows.h> //#define WM_MYMSG (WM_USER +100) LRESULT CALLBACK WndProc(HWND,UINT,WPAR ...

  5. python学习_2

    1.pycharm部分技巧 1)创建时路径尽量要避免中文2)用滚轮调整编辑器字体大小    1.file->setting...->editor->general 搜索'mouse' ...

  6. PCA算法学习(Matlab实现)

    PCA(主成分分析)算法,主要用于数据降维,保留了数据集中对方差贡献最大的若干个特征来达到简化数据集的目的. 实现数据降维的步骤: 1.将原始数据中的每一个样本用向量表示,把所有样本组合起来构成一个矩 ...

  7. Surf特征提取分析

    Surf特征提取分析 Surf Hessian SIFT 读"H.Bay, T. Tuytelaars, L. V. Gool, SURF:Speed Up Robust Features[ ...

  8. 图像识别基本算法之SURF

    图像识别.人脸识别可行的算法有很多.但是作为学习,如果能理清这个问题研究的历程及其主线,会对你深入理解当前研究最新的发展有很多帮助.本文是自己在学习过程中的笔记,大多内容来自于网络,出处请参考最后的引 ...

  9. OpenCV中的SURF算法介绍

    SURF:speed up robust feature,翻译为快速鲁棒特征.首先就其中涉及到的特征点和描述符做一些简单的介绍: 特征点和描述符 特征点分为两类:狭义特征点和广义特征点.狭义特征点的位 ...

随机推荐

  1. Python 强大而易用的文件操作(转载)

    在Python中可以很方便地做一些诸如浏览目录,创建文件夹,删除文件夹等等的操作. 对文件系统的访问大多通过os模块来实现,因为Python是多平台的,os模块只是前端,具体的实现还是由具体的系统来完 ...

  2. about Q&A in installing linux[centos6,7]

    keywords:grub1,grub2,gnome,kde, question describe:install centos7 by U disk,出现问题, 解决办法: install cent ...

  3. 如何在dapper中获取刚插入行的ID

    二话不说: 1.先建立个表 CREATE TABLE [dbo].[UserInfo](    [ID] [int] IDENTITY(1,1) NOT NULL,    [UserName] [nc ...

  4. To fix sql server 2008 r2 Evaluation period has expired by change the key

    PTTFM-X467G-P7RH2-3Q6CG-4DMYB 数据中心版:PTTFM-X467G-P7RH2-3Q6CG-4DMYB   测试可用 开 发者 版:MC46H-JQR3C-2JRHY-XY ...

  5. pywinauto二次封装(pywinnat.py)

    将pywinauto常用方法进行封装,使得pywinauto用起来更简单 #头文件的引入 from pywinauto import application from pywinauto import ...

  6. [XJOI NOI2015模拟题13] A 神奇的矩阵 【分块】

    题目链接:XJOI NOI2015-13 A 题目分析 首先,题目定义的这种矩阵有一个神奇的性质,第 4 行与第 2 行相同,于是第 5 行也就与第 3 行相同,后面的也是一样. 因此矩阵可以看做只有 ...

  7. Firefly是什么?有什么特点?

    原地址:http://bbs.gameres.com/forum.php?mod=viewthread&tid=219285 Firefly是免费.开源.稳定.快速扩展.能 “热更新”的分布式 ...

  8. PHP dirname() 函数

    定义和用法 dirname() 函数返回路径中的目录部分. 语法 dirname(path) 参数 描述 path 必需.规定要检查的路径. 说明 path 参数是一个包含有指向一个文件的全路径的字符 ...

  9. js设置radio选中

    在页面数据绑定时,经常会遇到给radio设置选中,以下是我写的js方法,经测试可以使用.欢迎拍砖 <html> <head> <script type="tex ...

  10. JavaWeb学习总结(十三)——使用Session防止表单重复提交

    在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交. 一.表单重复提 ...