• 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
    ",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 public19   //...
    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的生命值查看不存在写操作,多个用户同时查看没有任何影响,不需要加锁的,这里只是想提供一些额外的共同操作,以此来说明代码的简洁性及可扩展性是如此的重要。
    -------------------------------------------

  • 相关阅读:
    目录(爬虫)
    目录(自动化开发)
    目录(Python基础)
    目录(Django开发)
    C#Revit二次开发之-一键切换构件连接顺序 SwitchJoinOrder
    Revit常用的元素过滤方法
    C#之txt的数据写入
    惰性加载
    python mysql and ORM
    Python之常用模块学习(二)
  • 原文地址:https://www.cnblogs.com/Happyhe/p/4086734.html
Copyright © 2020-2023  润新知