您的位置:首页 > 编程语言

"基于对象" 和 "面向对象"

2009-10-13 12:09 561 查看
有网友学生针对《白话C++》问到一个小事, 有关虚函数和多态:

“ 如果说虚函数是为了让派生类的对象拥有特别、与人不同的个性的话。那么定义一个基类的堆变量:却用派生类对堆变量进行实例化:

Code:

Person *someone;  

someone=new Beauty; 

这有什么用?有什么好处么?与其这样,为什么不干脆把基类、派生类对象分别实例化? ”

简单回一下:

基类,派生类分别实例化,也很常见.这通常叫做OB编程("基于对象"),在这种情况下,派生类和基类都是具体的东西,只不过派生类对基类在功能有所扩 展......但在OO方法中,抽象是很重要的. 所谓抽象 和具体,通常就是:基类代表"抽象",派生类代表"具体". 比如:基类是:飞行物(抽象)...而派生类:飞机,老鹰....(具体,或叫具象). 如果你要写一个打飞行物的程序,我们当然希望射击逻辑只对"飞行物"写一次,而不是针对各种各样的派生类.

基类作为一种“抽象”,它只是提出要求:“我要求我的派生类必须提供哪些功能” ——这正是“类别”所代表的含义。我们偶尔会骂别人:“你简直就不是人”。为什么这么骂? 那是因为,某些社会的,公众的道德规定了“人类”应该具备的“功能”,如果一个人被社会唾弃,那么他肯定是:

1)、做了一些非人类功能范围内的事。 (比如他拥有一些只有禽兽才具备的功能)。

2)、缺失一些人类必须拥有的功能。(比如,善良。作为一个对象,他发现自己居没有这个功能)

回到飞行物的例子,再常见不过了。在飞行物射击类游戏中,它通常要具备:

1、能飞(不然怎么叫飞物)?

2、能判断是否被击中?

3、被击种以后,要有一些反应。

4、被击,但未中的情况下,也来点反应……

Code:

class Flyer  //不是苍蝇 :)

{  

public:  

        virtual void Flying() = 0; //我会飞  

        virtual bool HitTest(int x, int y, Bullet const& bullet) = 0; //被子弹击中了吗?  

        virtual void OnHitted() = 0; //被击中怎么办?  

        virtual void OnEscape() = 0;  //躲过时怎么办?  

};  

好,承认这个抽象可能写得不好,比如HitTest函数,也许不需要x,y,而由bullet提供自己的位置……另外,飞行物如何画到屏幕的函数没提供……但这些都不是重点。重点是,这是一个不错的抽象,所以它全都是“=0”的纯虚函数——代表Flyer只是提供要求,所有具体动作或功能,都需要派生类来实现。

但派生类会有哪些呢?理论是无边无尽的!该游戏的创造者,假设就你,可能只写以下一个:

Code:

class Airplane : public Flyer  

  

{  

  

    //具体实现略  

  

};  

 只有飞机一种射击目标,所以这个游戏或许只叫“打飞机”游戏更合适吧……但你们的老板有雄心壮志!果然这个游戏大卖特卖。不过,由于你的出色才华,你光荣地被M$给收买,于是又来了一个程序员补你的缺,假设是我,我呢,觉得光有飞机不太好玩,最好加下难度系数小一点的,于是我写这样一个派生类:

Code:

class Duck : public Flyer  //别不把鸭子当飞行物

{  

       //...  其余略

       virtual void OnHitted()  //被击中  

      {  

             cout << "Ga~~ Ga~~~" << endl; //纯属示意,真正的代码是往声卡输出

            //....  

      }  

   

};  

现在问题来了!我的前任(也就是你),当时根本不知道会有人写一个“鸭子”派生类,而铁打的营盘流水的兵,有一天,我也会离职(我好想去google啊~~555),我的续任者写什么什么派生类呢? 我现在不知道,反正是一种不明飞行物,噢对了,是UFO。

这里的典型问题就是:难道后任程序员,都必须对前任程序的代码深翻三尺地修改吗?对于BO,这是很有可能的。但对于OO,这个可以得到有效的缓解,因为,OO程序员,他懂得(争取)只对“抽象”对象写代码中的主要逻辑,比如一颗子弹飞出去,而此时满屏的飞行物,那么我的伟大的前任,伟大的OO程序员,再强调一次,就是你,是这样写这一段逻辑的:

Code:

...  

  

std::list<Flyer *> flyerLst; //一个列表存放当前屏幕上所有飞行物  

  

Bullet bullet; //一颗飞出的子弹.嗖嗖...  

  

...  

  

std::list<Flyer*>::iterator it; //列表的迭代器,请看《白话C++》的感受篇:Hello STL  

  

for (it=flyerLst.begin();  it != flyerLst.end(); ++it)  

  

{  

  

       //重点在这里:  

  

       Flyer* flyer = *it;   //为了清楚,我啰嗦一点  

  

        if (flyer->HitTest(bullet)) //判断是否击中?  

  

       {  

  

               flyer->OnHittest();  //如果flyer是duck对象,那它就会GaGa地叫~~~

  

               //....  

  

      }  

  

}  

看,上面的这段代码就是你当时写的,它一直都在,并且只处理Flyer对象!没有duck,ufo的单词吧?

继任者,我,只要往list里塞我鸭子就对了……

Code:

Flyer* flyer = new Duck;   //这里无所谓,也可以写 Duck* duck = new Duck;  

                     //但可能的话……  

  

flyerLst.push_back(flyer);  

作为前任,你不需要知道任何有关鸭子的具体的事……还有UFO……

--------------------

补充一点正题——我以为我在《白话 C++》里已经讲过了。new的时候,也就是上面代码中“但可能的话”,为什么也要这样写呢;

Flyer* flyer = new Duck;

这要看情况,当我们直接就可以知道当前要创建的是哪个对象,并且需要针对派生类作具体的设置时,那么写得直接明了一点,是必要的。想像一下,我们希望鸭子在成功躲闪子弹时,可能会因为紧张而下出蛋来,这时游戏者可以射中鸭蛋以获得积分。但鸭子区分公母,而且也并不是所有雌性鸭子能下蛋(发育问题)。所以我们在创建一只鸭子之后,需要调用一个函数,让它去尝试准备一下蛋的库存。

Code:

Duck* duck = new Duck;  

  

duck->PrepareEggs(); //或许只有鸭子需要准备蛋。  

  

flyerLst.push_back(duck);  

这种情况下,就叫做”具体事情具体分析“。交给派生类吧,如果用代表抽象的基类指针,代码编译不过去,不是吗?因为下蛋并不是所有飞行物都有的功能。

Code:

Flyer* flyer = new Duck;  

  

flyer->PrepareEggs(); //编译出错!  Flyer类没有这个函数

 

但不能这样就放过问题! 真的只有鸭子会下东西吗? 飞机在逃避过子弹时,(敌军飞行员)会不会一生气,扔下一些炸弹呢?嗯,而UFO难道不会扔下几个外星人? 要让游戏有扩展性,这样的预先设计上的弹性是必要的。所以,我们再改一改,将“下蛋”这样一件具体的事,抽象为“扔东西”!

Code:

class Flyer  

  

{  

  

       //    ... 原有的略  

  

      virtual void ThrowObject() = 0; //扔东西  

  

      virtual void PreareObject() = 0; //准备东西库存  

  

};  

多么美妙的设计 (一个正常的程序员的自我YY)!现在,我们可以实现的,再也不是仅仅创建时,也不仅仅是鸭子,更不仅仅是可以准备鸭蛋。 我们可以满足这样的需求:当飞行物在天空中坚持飞行了达到一定时长,就可以自动调用PrepareObject以准备一下库存,然后时机合适时,就通过ThrowObject()把各种各样的东西扔下来!如此,玩家就需要更聪明一些,比如可以让鸭子多存活一阵,因为我们想要它的蛋,而飞机可就不得了了,一定要快点干掉它,不然它的炸弹会越来越多……也许你就是游戏策划师,此时你是主动去设计这些游戏逻辑,也许你是程序员,此时你是被迫去实现这些逻辑,但好的程序设计是没有感情的,不分主动被动,它的目标就是为了让复杂的,不断发展变化的逻辑更容易实现,让程序员更爽,对应的,坏的设计就是为了让你,或者你的续任者痛不欲生的。

 

第三个问题,也是关键问题。一个好的OO设计师,当然不会写出完全是OB的代码,但也并不是一定要(客观上也做不到)抽象出所有对象的功能接口。上帝花了心思创造鸭子,所以鸭子总有鸭子的独立人生——临时励志一下:上帝创造了我们每个个体,所以我们总有自己的独到之处——这些独到是无法,或者说不适于抽象的——继续回归正题,但就算如此,有时我们也还是需要用基类指针来接受每个独特的派生类对象。

 

继续本游戏。想像一下,屏幕上的飞行物不停地被击中,落下,程序肯定要负责继续生成新的飞行物飞上天,请问,我们如何决定要生成哪种飞行物呢? 这是一个策略。策略可以很简单,完全用随机数决定是一种(抽奖吧),但最好是根据难度,关数,来决定一下只是什么东西飞上天。我们可以写一个函数:

Code:

Flyer* CreateNewFlyObject();  //一个重要的函数!它采用的策略好坏,是决定这个  

                 //“好玩度”的重要因素之一。  

虽然不是直接在new某个飞行物,但这个函数其实就是一复杂的new。它通过种种判断,来产生一个Flyer。至于这个Flyer是什么?调用处的代码在良好的设计下,是不应该去关心的,调用者只需用基类指针去承接:

Code:

Flyer* flyer = CreateNewFlyObject();  

这行话,代表的意思就是(伪代码): Flyer* flyer = new  Dock??  UFO? Airplane?? 管它创建出来的什么呢!我统统把它们当成飞行物就对了! (一个OO程序员的心声)。

结论还是一样:我们总是在努力地让关键代码尽量只处理基类(抽象),而不是派生类(具象)。

 

有读者要提问题,但……其实是一段广告啦,如果你没空,就别往下拉了。:)

 

 

有人问,看完《白话C++》会不会写得出上面的游戏?

答:没问题,因为《白话C++》不是只讲语法的书(虽然语法内容是最大一块),也会以著名的SDL为基础,至少讲解三个小游戏。前而已经有非编程专业同学,在只读了不到四分一内容情况下,通过自己扩展,写出了其中一个游戏

 

------------欢迎关注《白话 C++》的出版-------------------------

忙着房子的事……很久没来这里冒泡了,各位同学们还好吧~~~ 如果您想与我交流,请点击如下链接成为我的好友:
http://student.csdn.net/invite.php?u=112600&c=f635b3cf130f350c

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  游戏 oo 编程 扩展 c google
相关文章推荐