淺談多型——概念描述 (轉)

gugu99發表於2008-03-01
淺談多型——概念描述 (轉)[@more@]

淺談多型——概念描述 2001.9.25
 
  作者:Nicrosoft(奈軟 to:nicrosoft@sunistudio.com">nicrosoft@sunistudio.com)
  個人主頁:
  東日文件:/sunidoc.asp

  多型性,這個面向領域的核心概念,本身的內容博大精深,要以一文說清楚實在是不太可能。加之作者本人也還在不斷學習中,水平有限。因此本文只能描一下多型的輪廓,使讀者能夠了解個大概。如果有描的不準的地方,歡迎指出,或與作者探討(作者:)
 
  首先,什麼是多型(Polymorphisn)?按字面的意思就是“多種形狀”。我手頭的書上沒有找到一個多型的理論性的概念的描述。暫且引用一下CharlCalverts的對多型的描述吧——多型性是允許你將父物件設定成為和一個或更多的他的子物件相等的技術,賦值之後,父物件就可以根據當前賦值給它的子物件的特性以不同的方式運作(摘自“4 程式設計技術內幕”)。簡單的說,就是一句話:允許將子類型別的指標賦值給父類型別的指標。多型性在 Pascal和C++中都是透過虛(Virtual Function)實現的。
 
  好,接著是“虛擬函式”(或者是“虛方法”)。虛擬函式就是允許被其子類重新定義的成員函式。而子類重新定義父類虛擬函式的做法,稱為“覆蓋”(overr),或者稱為“重寫”。

  這裡有一個初學者經常混淆的概念。覆蓋(override)和過載(overload)。上面說了,覆蓋是指子類重新定義父類的虛擬函式的做法。而過載,是指允許存在多個同名函式,而這些函式的參數列不同(或許引數個數不同,或許引數型別不同,或許兩者都不同)。其實,過載的概念並不屬於“物件導向程式設計”,過載的實現是:根據函式不同的參數列,對同名函式的名稱做修飾,然後這些同名函式就成了不同的函式(至少對於編譯器來說是這樣的)。如,有兩個同名函式:function func(p:integer):integer;和function func(p:string):integer;。那麼編譯器做過修飾後的函式名稱可能是這樣的:int_func、str_func。對於這兩個函式的,在編譯器間就已經確定了,是靜態的(記住:是靜態)。也就是說,它們的地址在編譯期就繫結了(早繫結),因此,過載和多型無關!真正和多型相關的是“覆蓋”。當子類重新定義了父類的虛擬函式後,父類指標根據賦給它的不同的子類指標,動態(記住:是動態!)的呼叫屬於子類的該函式,這樣的函式呼叫在編譯期間是無法確定的(呼叫的子類的虛擬函式的地址無法給出)。因此,這樣的函式地址是在執行期繫結的(晚邦定)。結論就是:過載只是一種語言特性,與多型無關,與物件導向也無關!
 
  引用一句Bruce Eckel的話:“不要犯傻,如果它不是晚邦定,它就不是多型。”
 
  那麼,多型的作用是什麼呢?我們知道,封裝可以隱藏實現細節,使得程式碼模組化;繼承可以擴充套件已存在的程式碼模組(類);它們的目的都是為了——程式碼重用。而多型則是為了實現另一個目的——介面重用!而且現實往往是,要有效重用程式碼很難,而真正最具有價值的重用是介面重用,因為“介面是公司最有價值的資源。設計介面比用一堆類來實現這個介面更費時間。而且介面需要耗費更昂貴的人力的時間。”
 
  其實,繼承的為重用程式碼而存在的理由已經越來越薄弱,因為“組合”可以很好的取代繼承的擴充套件現有程式碼的功能,而且“組合”的表現更好(至少可以防止“類爆炸”)。因此筆者個人認為,繼承的存在很大程度上是作為“多型”的基礎而非擴充套件現有程式碼的方式了。
 
  什麼是介面重用?我們舉一個簡單的例子,假設我們有一個描述飛機的基類(Object Pascal語言描述,下同):
  type
  plane = class
  public
  procedure fly(); virtual; abstract; //起飛純虛擬函式
  procedure land(); virtual; abstract; //著陸純虛擬函式
  function modal() : string; virtual; abstract; //查尋型號純虛擬函式
  end;
 
  然後,我們從plane派生出兩個子類,直升機(copter)和噴氣式飛機(jet):
  copter = class(plane)
  private
  fModal : String;
  public
  constructor Create();
  destructor Destroy(); override;
  procedure fly(); override;
  procedure land(); override;
  function modal() : string; override;
  end;
 
  jet = class(plane)
  private
  fModal : String;
  public
  constructor Create();
  destructor Destroy(); override;
  procedure fly(); override;
  procedure land(); override;
  function modal() : string; override;
  end;
 
  現在,我們要完成一個飛機控制,有一個全域性的函式 plane_fly,它負責讓傳遞給它的飛機起飛,那麼,只需要這樣:
  procedure plane_fly(const pplane : plane);
  begin
  pplane.fly();
  end;
  就可以讓所有傳給它的飛機(plane的子類物件)正常起飛!不管是直升機還是噴氣機,甚至是現在還不存在的,以後會增加的飛碟。因為,每個子類都已經定義了自己的起飛方式。
 
  可以看到 plane_fly函式接受引數的是 plane類物件引用,而實際傳遞給它的都是 plane的子類物件,現在回想一下開頭所描述的“多型”:多型性是允許你將父物件設定成為和一個或更多的他的子物件相等的技術,賦值之後,父物件就可以根據當前賦值給它的子物件的特性以不同的方式運作。
 
  很顯然,parent = child; 就是多型的實質!因為直升機“是一種”飛機,噴氣機也“是一種”飛機,因此,所有對飛機的操作,都可以對它們操作,此時,飛機類就作為一種介面。
 
  多型的本質就是將子類型別的指標賦值給父類型別的指標(在OP中是引用),只要這樣的賦值發生了,多型也就產生了,因為實行了“向上對映”。
 
  應用多型的例子非常普遍,在Delphi的VCL類庫中,最典型的就是:TObject類有一個虛擬的Destroy虛構函式和一個非虛擬的Free函式。Free函式中是呼叫Destroy的。因此,當我們對任何物件(都是TObject的子類物件)呼叫 .Free();之後,都會 TObject.Free();,它會呼叫我們所使用的物件的解構函式 Destroy();。這就保證了任何型別的物件都可以正確地被析構。

  多型性作為物件導向最重要的特性,本文所提不過是滄海一粟,還有很多內容。如果可能,希望會有後文繼續探討多型。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-1000231/,如需轉載,請註明出處,否則將追究法律責任。

相關文章