1. 程式人生 > >"基於物件" 和 "面向物件"

"基於物件" 和 "面向物件"

有網友學生針對《白話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.         virtualvoid Flying() = 0; //我會飛
  5.         virtualbool HitTest(int x, int y, Bullet const& bullet) = 0; //被子彈擊中了嗎?
  6.         virtualvoid OnHitted() = 0; //被擊中怎麼辦?
  7.         virtualvoid OnEscape() = 0;  //躲過時怎麼辦?
  8. };  

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Flyer* flyer = new Duck;

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

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

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

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

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

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

多麼美妙的設計 (一個正常的程式設計師的自我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++》的出版-------------------------

相關推薦

"基於物件" "面向物件"

有網友學生針對《白話C++》問到一個小事, 有關虛擬函式和多型: “ 如果說虛擬函式是為了讓派生類的物件擁有特別、與人不同的個性的話。那麼定義一個基類的堆變數:卻用派生類對堆變數進行例項化: Code: Person *someone;   someone=ne

熟練使用Lua(四)面向物件基於table的面向物件實現(2)

myluaootest.lua –1. 基本原理 local Cal = {} function Cal:New(o) o = o or {} setmetatable(o, self) self.__index = self return o end functio

熟練使用Lua(四)面向物件基於table的面向物件實現(1)

轉:https://www.cnblogs.com/yao2yaoblog/p/6433553.html c++和java語言機制中本身帶有面向物件的內容,而lua設計的思想是超程式設計,沒有面向物件的實現。 但是利用lua的元表(matetable)機制,可以實現面向物件。要講清楚怎樣

ORM框架 面向物件程式設計

ORM框架: 1.SQLAlchemy:  - 作用   1.提供簡單的規則   2.自動轉換成SQL語句  - DB first/code first   DB fir

第037講:類物件面向物件程式設計

目錄 0. 請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式! 測試題 0. 以下程式碼體現了面向物件程式設計的什麼特徵? 1. 當程式設計師不想把同一段程式碼寫幾次,他們發明了函式解決了這種情況。當程式設計師已經有了一個類,而又想建立一個非常相近的新類,他們

《零基礎入門學習Python》(36)--類物件面向物件程式設計的相關知識

前言 Python3 面向物件 Python從設計之初就已經是一門面向物件的語言,正因為如此,在Python中建立一個類和物件是很容易的。本章節我們將詳細介紹Python的面向物件程式設計。 如果你以前沒有接觸過面向物件的程式語言,那你可能需要先了解一些面嚮物件語言的一

基於原型的面向物件程式設計

1.物件程式設計概述 JavaScript 中的所有事物都是物件:字串、數字、陣列、日期,等等。 在 JavaScript 中,物件是變數和函式集合,物件中的變數成為屬性(property),物件中的函式稱為方法(method),物件是擁有屬性和方法的資料。

面向過程”面向物件”的區別

面向過程就是分析出解決問題所需要的步驟,然後用函式把這些步驟一步一步實現,使用的時候一個一個依次呼叫就可以了;面向物件是把構成問題事務分解成各個物件,建立物件的目的不是為了完成一個步驟,而是為了描敘某個事物在整個解決問題的步驟中的行為。 [2]  可以拿生活中的例項來理解

基於uml的面向物件的概要設計

1. 什麼是概要設計?為什麼要進行概要設計?     白話解釋:概要設計,顧名思意,大概簡要的設計,大概簡要是從整體來說,不是說不準確含糊之意。設計什麼呢?前面我們進行了系統的需求分析,有兩個成果 --1--.系統用例圖--2--.類圖集合,所以我們的概要設計要在1.2的基

基於Oracle的面向物件技術基礎簡析

一、概述  物件是Oracle8i以上版本中的一個新的特性,物件實際是對一組資料和操作的封裝,物件的抽象就是類。在面向物件技術中,物件涉及到以下幾個重要的特性:   封裝性  通過對資料和操作的封裝,將使用者關心的資料和操作暴露出來作為介面,其他資料和操作則隱藏到物件內部,這

JSP學習筆記三之response物件request物件

接著上一篇,我們接著講JSP中的內建隱式物件。這篇部落格介紹的是request和response物件。 A. request物件      request物件是javax.servlet.http.HttpServletReq

jQuery物件DOM物件之間的區別以及轉換方法

jQuery物件和DOM物件之間的區別以及轉換方法 在實現<script>標籤中的程式碼內容的時候,經常都會使用DOM物件和jQuery物件。當實現的物件多的時候就容易搞混,這裡做一下總結 jQuery物件是包裝DOM物件後產生的,

Es6中Map物件Set物件的介紹及應用

map和set的方法,工作中有使用到,所以學習一下:   Map 物件 Map 物件儲存鍵值對。任何值(物件或者原始值) 都可以作為一個鍵或一個值。 var myMap = new Map(); myMap.set("bar", "baz"); myMap.set(1, "foo"

類,物件面向物件以及三大特徵

一、  類和物件 面向物件程式設計中兩個重要的概念:類和物件 1、簡單來說:物件的抽象化是類,類的具體化就是物件。類是一種抽象的概念,是對現實生活中事物的描述,類是對某一批物件的抽象,它不是實際存在的事物。物件是一個實際存在的實體,從這個意義上講,萬物都是物件。我們日常所說的人,都是人

9.13 檢視層之請求物件響應物件

解耦: 從瀏覽器訪問都是get請求,post請求可以是從form表單method=post       二、請求物件:         匹配到根路徑:    

魯棒圖的三元素:抽象物件,實體物件控制物件

魯棒圖簡介  ADMEMS方法推薦以魯棒圖來輔助初步設計。那麼,什麼是魯棒圖呢? 8.2.1  魯棒圖的3種元素 魯棒圖包含3種元素(如圖8-2所示),它們分別是邊界物件、控制物件、實體物件: 邊界物件對模擬外部環境和未來系統之間的互動進行建模。邊界物件負責接收外部輸入,處理內部內

python下,類物件例項物件區別,類變數例項變數區別

Y14 一、類物件和例項物件 簡短理論: 類物件是將具有相似屬性和方法的物件總結抽象為類物件,可以定義相似的一些屬性和方法,不同的例項物件去引用類物件的屬性和方法,能減少程式碼的重複率。 例項物件又稱例項化物件,不是抽象而是一類物件中具體的一例物件。 比

Java關於實體物件Map物件之間的轉換

/** * 實體物件轉成Map * @param obj 實體物件 * @return */ public static Map<String, Object> object2Map(Object obj) { Ma

jQuery物件DOM物件字串之間的轉化

如何完成jQuery物件和DOM物件和字串之間的轉化 其實非常簡單: 字串---------->jQuery物件 $(HTML字串): $(' 我是祖國的一朵小花 ') 待新增列表 jQuery物件---------->DOM物件 下標選取法(原理:

Day3-Date物件Array物件

Day3-Date物件和Array物件 Date物件 宣告一個Date(日期)物件; var d = new Date(); 獲取當前系統時間; var str = d.getFullYear()+"/"+(d.getMonth()+1)+"/"+