"基於對象" 和 "物件導向"

來源:互聯網
上載者:User

有網友學生針對《白話C++》問到一個小事, 有關虛函數和多態:

“ 如果說虛函數是為了讓衍生類別的對象擁有特別、與人不同的個性的話。那麼定義一個基類的堆變數:卻用衍生類別對堆變數進行執行個體化:

Code:
  1. Person *someone;  
  2. someone=new Beauty; 

這有什麼用?有什麼好處嗎?與其這樣,為什麼不乾脆把基類、衍生類別對象分別執行個體化? ”

簡單回一下:

基類,衍生類別分別執行個體化,也很常見.這通常叫做OB編程("基於對象"),在這種情況下,衍生類別和基類都是具體的東西,只不過衍生類別對基類在功能有所擴 展......但在OO方法中,抽象是很重要的. 所謂抽象 和具體,通常就是:基類代表"抽象",衍生類別代表"具體". 比如:基類是:飛行物(抽象)...而衍生類別:飛機,老鷹....(具體,或叫具象). 如果你要寫一個打飛行物的程式,我們當然希望射擊邏輯只對"飛行物"寫一次,而不是針對各種各樣的衍生類別.

基類作為一種“抽象”,它只是提出要求:“我要求我的衍生類別必須提供哪些功能” ——這正是“類別”所代表的含義。我們偶爾會罵別人:“你簡直就不是人”。為什麼這麼罵? 那是因為,某些社會的,公眾的道德規定了“人類”應該具備的“功能”,如果一個人被社會唾棄,那麼他肯定是:

1)、做了一些非人類功能範圍內的事。 (比如他擁有一些只有禽獸才具備的功能)。

2)、缺失一些人類必須擁有的功能。(比如,善良。作為一個對象,他發現自己居沒有這個功能)

回到飛行物的例子,再常見不過了。在飛行物射擊類遊戲中,它通常要具備:

1、能飛(不然怎麼叫飛物)?

2、能判斷是否被擊中?

3、被擊種以後,要有一些反應。

4、被擊,但未中的情況下,也來點反應……

Code:
  1. class Flyer  //不是蒼蠅
  2. {  
  3. public:  
  4.         virtual void Flying() = 0; //我會飛  
  5.         virtual bool HitTest(int x, int y, Bullet const& bullet) = 0; //被子彈擊中了嗎?  
  6.         virtual void OnHitted() = 0; //被擊中怎麼辦?  
  7.         virtual void OnEscape() = 0;  //躲過時怎麼辦?  
  8. };  

好,承認這個抽象可能寫得不好,比如HitTest函數,也許不需要x,y,而由bullet提供自己的位置……另外,飛行物如何畫到螢幕的函數沒提供……但這些都不是重點。重點是,這是一個不錯的抽象,所以它全都是“=0”的純虛函數——代表Flyer只是提供要求,所有具體動作或功能,都需要衍生類別來實現。

但衍生類別會有哪些呢?理論是無邊無盡的!該遊戲的創造者,假設就你,可能唯寫以下一個:

Code:
  1. class Airplane : public Flyer  
  2.   
  3. {  
  4.   
  5.     //具體實現略  
  6.   
  7. };  

 只有飛機一種射擊目標,所以這個遊戲或許只叫“打飛機”遊戲更合適吧……但你們的老闆有雄心壯志!果然這個遊戲大賣特賣。不過,由於你的出色才華,你光榮地被M$給收買,於是又來了一個程式員補你的缺,假設是我,我呢,覺得光有飛機不太好玩,最好加下難度係數小一點的,於是我寫這樣一個衍生類別:

Code:
  1. class Duck : public Flyer  //別不把鴨子當飛行物
  2. {  
  3.        //...  其餘略
  4.        virtual void OnHitted()  //被擊中  
  5.       {  
  6.              cout << "Ga~~ Ga~~~" << endl; //純屬示意,真正的代碼是往音效卡輸出
  7.             //....  
  8.       }  
  9.    
  10. };  

現在問題來了!我的前任(也就是你),當時根本不知道會有人寫一個“鴨子”衍生類別,而鐵打的營盤流水的兵,有一天,我也會離職(我好想去google啊~~555),我的續任者寫什麼什麼衍生類別呢? 我現在不知道,反正是一種不明飛行物,噢對了,是UFO。

這裡的典型問題就是:難道後任程式員,都必須對前任程式的代碼深翻三尺地修改嗎?對於BO,這是很有可能的。但對於OO,這個可以得到有效緩解,因為,OO程式員,他懂得(爭取)只對“抽象”對象寫代碼中的主要邏輯,比如一顆子彈飛出去,而此時滿屏的飛行物,那麼我的偉大的前任,偉大的OO程式員,再強調一次,就是你,是這樣寫這一段邏輯的:

Code:
  1. ...  
  2.   
  3. std::list<Flyer *> flyerLst; //一個列表存放當前螢幕上所有飛行物  
  4.   
  5. Bullet bullet; //一顆飛出的子彈.嗖嗖...  
  6.   
  7. ...  
  8.   
  9. std::list<Flyer*>::iterator it; //列表的迭代器,請看《白話C++》的感受篇:Hello STL  
  10.   
  11. for (it=flyerLst.begin();  it != flyerLst.end(); ++it)  
  12.   
  13. {  
  14.   
  15.        //重點在這裡:  
  16.   
  17.        Flyer* flyer = *it;   //為了清楚,我囉嗦一點  
  18.   
  19.         if (flyer->HitTest(bullet)) //判斷是否擊中?  
  20.   
  21.        {  
  22.   
  23.                flyer->OnHittest();  //如果flyer是duck對象,那它就會GaGa地叫~~~
  24.   
  25.                //....  
  26.   
  27.       }  
  28.   
  29. }  

看,上面的這段代碼就是你當時寫的,它一直都在,並且只處理Flyer對象!沒有duck,ufo的單詞吧?

繼任者,我,只要往list裡塞我鴨子就對了……

Code:
  1. Flyer* flyer = new Duck;   //這裡無所謂,也可以寫 Duck* duck = new Duck;  
  2.                      //但可能的話……  
  3.   
  4. flyerLst.push_back(flyer);  

作為前任,你不需要知道任何有關鴨子的具體的事……還有UFO……

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

補充一點正題——我以為我在《白話 C++》裡已經講過了。new的時候,也就是上面代碼中“但可能的話”,為什麼也要這樣寫呢;

Flyer* flyer = new Duck;

這要看情況,當我們直接就可以知道當前要建立的是哪個對象,並且需要針對衍生類別作具體的設定時,那麼寫得直接明了一點,是必要的。想像一下,我們希望鴨子在成功躲閃子彈時,可能會因為緊張而下出蛋來,這時遊戲者可以射中鴨蛋以獲得積分。但鴨子區分公母,而且也並不是所有雌性鴨子能下蛋(發育問題)。所以我們在建立一隻鴨子之後,需要調用一個函數,讓它去嘗試準備一下蛋的庫存。

Code:
  1. Duck* duck = new Duck;  
  2.   
  3. duck->PrepareEggs(); //或許只有鴨子需要準備蛋。  
  4.   
  5. flyerLst.push_back(duck);  

這種情況下,就叫做”具體事情具體分析“。交給衍生類別吧,如果用代表抽象的基類指標,代碼編譯不過去,不是嗎?因為下蛋並不是所有飛行物都有的功能。

Code:
  1. Flyer* flyer = new Duck;  
  2.   
  3. flyer->PrepareEggs(); //編譯出錯!  Flyer類沒有這個函數

 

但不能這樣就放過問題! 真的只有鴨子會下東西嗎? 飛機在逃避過子彈時,(敵軍飛行員)會不會一生氣,扔下一些炸彈呢?嗯,而UFO難道不會扔下幾個外星人? 要讓遊戲有擴充性,這樣的預先設計上的彈性是必要的。所以,我們再改一改,將“下蛋”這樣一件具體的事,抽象為“扔東西”!

Code:
  1. class Flyer  
  2.   
  3. {  
  4.   
  5.        //    ... 原有的略  
  6.   
  7.       virtual void ThrowObject() = 0; //扔東西  
  8.   
  9.       virtual void PreareObject() = 0; //準備東西庫存  
  10.   
  11. };  

多麼美妙的設計 (一個正常的程式員的自我YY)!現在,我們可以實現的,再也不是僅僅建立時,也不僅僅是鴨子,更不僅僅是可以準備鴨蛋。 我們可以滿足這樣的需求:當飛行物在天空中堅持飛行了達到一定時間長度,就可以自動調用PrepareObject以準備一下庫存,然後時機合適時,就通過ThrowObject()把各種各樣的東西扔下來!如此,玩家就需要更聰明一些,比如可以讓鴨子多存活一陣,因為我們想要它的蛋,而飛機可就不得了了,一定要快點幹掉它,不然它的炸彈會越來越多……也許你就是遊戲策劃師,此時你是主動去設計這些遊戲邏輯,也許你是程式員,此時你是被迫去實現這些邏輯,但好的程式設計是沒有感情的,不分主動被動,它的目標就是為了讓複雜的,不斷髮展變化的邏輯更容易實現,讓程式員更爽,對應的,壞的設計就是為了讓你,或者你的續任者痛不欲生的。

 

第三個問題,也是關鍵問題。一個好的OO設計師,當然不會寫出完全是OB的代碼,但也並不是一定要(客觀上也做不到)抽象出所有對象的功能介面。上帝花了心思創造鴨子,所以鴨子總有鴨子的獨立人生——臨時勵志一下:上帝創造了我們每個個體,所以我們總有自己的獨到之處——這些獨到是無法,或者說不適於抽象的——繼續迴歸正題,但就算如此,有時我們也還是需要用基類指標來接受每個獨特的衍生類別對象。

 

繼續本遊戲。想像一下,螢幕上的飛行物不停地被擊中,落下,程式肯定要負責繼續產生新的飛行物飛上天,請問,我們如何決定要產生哪種飛行物呢? 這是一個策略。策略可以很簡單,完全用隨機數決定是一種(抽獎吧),但最好是根據難度,關數,來決定一下只是什麼東西飛上天。我們可以寫一個函數:

Code:
  1. Flyer* CreateNewFlyObject();  //一個重要的函數!它採用的策略好壞,是決定這個  
  2.                  //“好玩度”的重要因素之一。  

雖然不是直接在new某個飛行物,但這個函數其實就是一複雜的new。它通過種種判斷,來產生一個Flyer。至於這個Flyer是什嗎?調用處的代碼在良好的設計下,是不應該去關心的,調用者只需用基類指標去承接:

Code:
  1. Flyer* flyer = CreateNewFlyObject();  

這行話,代表的意思就是(虛擬碼): Flyer* flyer = new  Dock??  UFO? Airplane?? 管它建立出來的什麼呢!我統統把它們當成飛行物就對了! (一個OO程式員的心聲)。

結論還是一樣:我們總是在努力地讓關鍵代碼盡量只處理基類(抽象),而不是衍生類別(具象)。

 

有讀者要提問題,但……其實是一段廣告啦,如果你沒空,就別往下拉了。:)

 

 

有人問,看完《白話C++》會不會寫得出上面的遊戲?

答:沒問題,因為《白話C++》不是只講文法的書(雖然文法內容是最大一塊),也會以著名的SDL為基礎,至少講解三個小遊戲。前而已經有非編程專業同學,在唯讀了不到四分一內容情況下,通過自己擴充,寫出了其中一個遊戲。

 

------------歡迎關注《白話 C++》的出版-------------------------

忙著房子的事……很久沒來這裡冒泡了,各位同學們還好吧~~~ 如果您想與我交流,請點擊如下連結成為我的好友:
http://student.csdn.net/invite.php?u=112600&c=f635b3cf130f350c

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.