c++学习_2

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

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

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

 1 // 定义一个角色扮演无关的抽象基类
 2 class gameNPC{
 3 public:
 4   //...(省略构造函数和析构函数)
 5   virtual int calcCurrHealth() const = 0;
 6 private:
 7   //...(省略具体的成员变量)
 8 };
 9 // 定义一个野怪的类,以public的方式继承于gameNPC
10 class gameMinion : public gameNPC{
11 public:
12   //...(省略构造函数和析构函数)
13   // 重写继承而来的纯虚函数
14   virtual int calcCurrHealth() const
15   {
16     //获取互斥锁(mutex)
17     Mutex* pMutex = new Mutex(...);
18     //...
19
20     //计算当前野怪的剩余生命值
21     //...
22
23     //释放互斥锁
24     delete pMutex;
25   }
26 private:
27   //...(省略具体的成员变量)
28 };
29 // 定义一个boss的类,以public的方式继承于gameNPC
30 class gameBoss : public gameNPC{
31 public:
32   //...(省略构造函数和析构函数)
33   // 重写继承而来的纯虚函数
34   virtual int calcCurrHealth() const
35   {
36     //获取互斥锁(mutex)
37     Mutex* pMutex = new Mutex(...);
38     //...
39
40     //计算当前boss的剩余生命值
41     //...
42
43     //释放互斥锁
44     delete pMutex;
45   }
46 private:
47   //...(省略具体的成员变量)
48 };

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

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

 1 // 定义一个角色扮演无关的抽象基类
 2 class gameNPC{
 3 public:
 4   //...(省略构造函数和析构函数)
 5   // 完成任务的non-virtual接口,子类会继承之
 6   int currHealth() const;
 7   {
 8     //获取互斥锁(mutex)
 9     Mutex* pMutex = new Mutex(...);
10     //...
11
12     //计算当前boss的剩余生命值
13     int healthVal = calcCurrHealth();
14
15     //释放互斥锁
16     delete pMutex;
17     return healthVal ;
18   }
19 private:
20   // 抽离出不相同的部分(计算不同对象的剩余生命值)
21   virtual int calcCurrHealth() const = 0;
22   //...(省略具体的成员变量)
23 };
24 // 定义一个野怪的类,以public的方式继承于gameNPC
25 class gameMinion : public gameNPC{
26 public:
27   //...(省略构造函数和析构函数)
28
29 private:
30   // 计算野怪的剩余生命值
31   virtual int calcCurrHealth() const
32   {
33   //...
34   }
35   //...(省略具体的成员变量)
36 };
37 // 定义一个boss的类,以public的方式继承于gameNPC
38 class gameBoss : public gameNPC{
39 public:
40   //...(省略构造函数和析构函数)
41
42 private:
43   // 计算boss的剩余生命值
44   virtual int calcCurrHealth() const
45   {
46   //...
47   }
48   //...(省略具体的成员变量)
49 };
50
51 //上面的NVI方法能否完成任务呢?测试一下就知道,测试代码如下:
52 gameNPC* pNPC = new gameMinion();
53 printf("%d\n",pNPC->currHealth()); //打印出野怪当前的剩余生命值
54 pNPC = new gameBoss();
55 printf("%d\n",pNPC->currHealth()); //打印出boss当前的剩余生命值

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

 1 //将计算不同对象的剩余生命值这个任务抽离出来,定义一个接口类
 2 class calcHealthInterface{
 3 public:
 4   //...
 5   virtual int calcHealth() const = 0;
 6 };
 7 class calcHealthMinion : public calcHealthInterface{
 8 public:
 9   //...
10
11   //重写calcHealth函数,计算野怪的剩余生命值
12   virtual int calcHealth() const
13   {
14   //...
15   }
16 };
17 class calcHealthBoss : public calcHealthInterface{
18 public:
19   //...
20
21   //重写calcHealth函数,计算boss的剩余生命值
22   virtual int calcHealth() const
23   {
24   //...
25   }
26 };
27 // 定义一个角色扮演无关的抽象基类
28 class gameNPC{
29 public:
30   explicit gameNPC(calcHealthInterface* pFunc):m_pHealthFunc(pFunc)
31   { }
32   virtual ~gameNPC() { //...}
33   // 完成任务的non-virtual接口,子类会继承之
34   int currHealth() const;
35   {
36     //获取互斥锁(mutex)
37     Mutex* pMutex = new Mutex(...);
38     //...
39
40     //计算当前boss的剩余生命值
41     int healthVal = m_pHealthFunc->calcHealth();
42
43     //释放互斥锁
44     delete pMutex;
45     return healthVal;
46   }
47 private:
48   calcHealthInterface* m_pHealthFunc;
49   //...(省略具体的成员变量)
50 };
51 // 定义一个野怪的类,以public的方式继承于gameNPC
52 class gameMinion : public gameNPC{
53 public:
54   explicit gameMinion(calcHealthInterface* pFunc):gameNPC(pFunc)
55   { }
56   virtual ~gameMinion() { //... }
57
58 private:
59   //...(省略具体的成员变量)
60 };
61 // 定义一个boss的类,以public的方式继承于gameNRP
62 class gameBoss : public gameNPC{
63 public:
64   explicit gameBoss(calcHealthInterface* pFunc):gameNPC(pFunc)
65   { }
66   virtual ~gameBoss() { //... }
67
68 private:
69   //...(省略具体的成员变量)
70 };
71 //下面是测试代码:
72 calcHealthInterface* pFuncInterface = new calcHealthMinion();
73 gameNPC* pNPC = new gameMinion(pFuncInterface);
74 printf("%d\n",pNPC->currHealth()); //打印出是哪个对象的剩余生命值呢???
75 pFuncInterface = new calcHealthBoss();
76 pNPC = new gameBoss(pFuncInterface);
77 printf("%d\n",pNPC->currHealth()); //打印出是哪个对象的剩余生命值呢???

总结

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

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

时间: 2024-10-02 21:01:36

c++学习_2的相关文章

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

在上篇博客特征点检测学习_1(sift算法) 中简单介绍了经典的sift算法,sift算法比较稳定,检测到的特征点也比较多,其最大的确定是计算复杂度较高.后面有不少学者对其进行了改进,其中比较出名的就是本文要介绍的surf算法,surf的中文意思为快速鲁棒特征.本文不是专门介绍surf所有理论(最好的理论是作者的论文)的,只是对surf算法进行了下整理,方便以后查阅. 网上有些文章对surf做了介绍,比如: http://wuzizhang.blog.163.com/blog/static/78

Smarty模板的学习_2

使用配置文件给变量赋值 一,在配置类中设置配置文件的目录名字和路径 $smarty->setConfigDir(ROOT."/configs"); 二,创建目录文件  configs,里面再创建一个后缀是 .conf  的文件 然后再配置文件中定义变量,如下所示 bgcolor=red; width=100px; height=200px; color=black; border='2'; 三,在使用模板的tpl文件的前头引入配置文件 <{config_load file=

python学习_2

1.pycharm部分技巧 1)创建时路径尽量要避免中文2)用滚轮调整编辑器字体大小    1.file->setting...->editor->general 搜索'mouse'    2.找到并打勾“change font size(zoom) with ctrl+Mouse Wheel”    3.ok或apply保存设置    4.“ctrl+鼠标滚轮”就可以调整字体大 3)在代码区域空白处,右击出现菜单,“run”可以直接运行当前的文件4)写代码时,红色波浪线代表语法错误,无

动手学深度学习_2.1_tensor

数据操作 1 import torch 2 3 torch.manual_seed(0) 4 torch.cuda.manual_seed(0) 5 print(torch.__version__) # 1.3.1 创建tensor 1 # 创建一个5x3的未初始化的tensor 2 x = torch.empty(5, 3) 3 print(x) 4 5 # tensor([[1.3563e-19, 1.3563e-19, 7.9717e-10], 6 # [5.8270e-10, 5.827

动手学深度学习_2.2_autograd

Tensor import torch x = torch.ones(2, 2, requires_grad=True) # 将其属性.requires_grad设置为True,它将开始追踪(track)在其上的所有操作.完成计算后,可以调用.backward()来完成所有梯度计算 print(x) print(x.grad_fn) # 每个Tensor都有一个.grad_fn属性,该属性即创建该Tensor的Function(除非用户创建的Tensors时设置了grad_fn=None) #

Surf特征提取分析

Surf特征提取分析 Surf Hessian SIFT 读"H.Bay, T. Tuytelaars, L. V. Gool, SURF:Speed Up Robust Features[J],ECCV,2006"笔记 SURF:Speed Up Robust Features,加速鲁棒特征. 我觉得SURF是SIFT特征的一种近似计算,在相似性能甚至更好性能的同时提高了算法的速度.这些近似体现在 在尺度空间中,使用box filtes与原图像卷积,而不是使用DoG算子 确定关键点方

20145235 《信息安全系统设计基础》第09周学习总结 _2

20145235 <信息安全系统设计基础>第09周学习总结 _1 习题10.1 首先遇到的问题就是usr/include里面没有csapp.h和csapp.c从晚上下载了一个压缩包,并进行了解压tar -xf code.tar 再将里面的csapp.h和csapp.c放到usr/include里面. 然后编译运行c101.c.描述符返回值为3. 代码托管 感想与问题 我看了大家写的博客,认为这个习题最重要的是让我们理解描述符,在打开foo.txt的时候描述符池中的 0.1.2已被用了,然后打开

Computer Network学习笔记_2

1_5 Traceroute,一种command tool,可以看network内部信息,ISP内部信息. 1_6 理解Network的模块化封装.学习Protocols和Layers,这种构建computer networks的关键机制.封装是越底层的protocol越封装在外面,形成一个protocol stack.每一层都在message加自己的header.当Browser和Server传输信息时,从Brower向下逐层封装,通过物理层传输,再从下到上demultiplexing.在不同

IOS学习入门准备-C—_2

用命令行编译C文件我们已经知道了 简单谈一下他的编译过程.. 首先我们创建了test.c的C文件 也就是源文件, 接着预编译,再由编译器编译为汇编代码, 汇编器把汇编代码编译为目标文件(.o),最后由连接器把所有目标文件与库文件连接起来生成可执行文件 源文件->预编译文件->汇编代码文件->目标文件->链接后生成可执行文件: 下面学习头文件与实现文件的意义 我们都知道C语言编程第一行是什么呢 1 #include <stdio.h> 2 int main(){ 3 pr