这里承接上一篇文章,继续记录关于继承的那些事儿...
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 ",pNPC->currHealth()); //打印出野怪当前的剩余生命值 54 pNPC = new gameBoss(); 55 printf("%d ",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 ",pNPC->currHealth()); //打印出是哪个对象的剩余生命值呢??? 75 pFuncInterface = new calcHealthBoss(); 76 pNPC = new gameBoss(pFuncInterface); 77 printf("%d ",pNPC->currHealth()); //打印出是哪个对象的剩余生命值呢???
总结
看上去方法三较前面两种方法,显得更复杂;哪里能体现出该模式的优点呢?从功能扩展的角度看,如果出现了其他角色扮演无关的对象出现,它们剩余生命值的计算方法和前面的野怪或boss的不同,这时需要做的事情很简单,从calcHealthInterface类派生一个接口类并重写calcHealth函数,然后从gameNPC类派生对应的对象,具体做法和gameMinion及gameBoss一样,OK!!!这样就完成的功能拓展,用户调用时只需要使用基类指针即可,很方便,不是么?
-------------------------------------------
上面提出的任务纯属杜撰不切实际,因为玩游戏时,对于boss的生命值查看不存在写操作,多个用户同时查看没有任何影响,不需要加锁的,这里只是想提供一些额外的共同操作,以此来说明代码的简洁性及可扩展性是如此的重要。
-------------------------------------------