麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 編程 > Delphi > 正文

我的文章-《剖析Delphi中的多態》

2019-11-18 18:10:47
字體:
來源:轉載
供稿:網友
 

剖析Delphi中的多態

1什么是多態? 2
1.1概念 2
1.2多態的意義 2
1.3多態在delphi中如何實現的? 2
1.3.1 繼承(Inheritance) 2
1.3.2 虛方法、動態方法與抽象方法,VMT/DMT,靜態綁定與動態綁定 2
1.3.3 重載(Overload)與多態 2
1.4多態種類的探討 2
1.4.1 兩級多態 2
1.4.2 不安全的多態 2
2 VCL中多態的應用 2
2.1構造與析構方法 2
2.2 Tstrings 2
2.3其他(請soul來補充) 2

摘  要 多態是面向對象的靈魂所在,理解多態是掌握面向對象技術的關鍵之一,本文著重分析多態的基本原理、多態的實質以及在VCL中的應用。
關鍵字 多態、繼承、面向對象、VCL、虛函數(virtual Method)、覆載(override)


問題
多態是面向對象的靈魂所在,理解多態是掌握面向對象技術的關鍵之一。但是到底什么是多態?多態有何意義?怎么實現多態?多態的概念我能懂,但不知道如何使用以及什么時候該使用呢?請看本文細細道來。
專家分析
   天地生物(物,即對象),千變萬化;而在計算機世界里,只有一行行機器指令,兩者似乎毫不相干,過去要用計算機語言來很好地描述現實世界是一件很困難的事情,雖然有人用C語言寫出面向對象的程序來,但我敢斷定其寫法是極其煩瑣的,直到面向對象(Oriented-Object 簡稱OO)的出現,一切都隨之改觀,整個軟件業發生了翻天覆地的變化,從編程語言的變化開始,出現了一系列面向對象編程語言(OOP)如SmallTalk、C++、java、Object Pascal、C#等;隨之各種面向對象開發工具也出現了如VC、Delphi、BCB、JBuilder等,并出現了許多優秀的類庫如VCL、.net Framework和一些商業類庫等;再發展到了面向對象的設計(OOD),面向對象的分析(OOA)以及面向對象的數據庫(OODB),面向對象技術幾乎貫穿了整個軟件領域,程序員的思考方式也發生了根本性的變化!在一些OO純化論者眼中,一切皆是對象!雖然我不完全同意這種看法。但我認為這種方式最符合人們的思維習慣,它使程序員能集中精力考慮業務邏輯,由計算機來完成面向對象到機器指令的轉換(由面向對象的編譯器來完成),程序員的大腦從此解放出來了!這是一場革命!
面向對象的核心內容是對象,封裝,繼承,多態和消息機制,其中多態就是為了描述現實世界的多樣性的,也是面向對象中最為重要的特性,可以這么說,不掌握多態,就沒有真正地掌握面向對象技術。
1什么是多態?
1.1概念
多態的概念眾說紛紜,下面是幾種代表性的說法:
“This ability to manipulate more than one type with a pointer or a reference to a base classis spoken of as polymorphism” (《C++ PRimer》第838頁)。即用基類的指針/引用來操作多種類(基類和其派生類)的對象的能力稱之為多態。它是從語言實現的角度來考慮的。
“polymorphism provides another dimension of separation of interface from implementation, to decouple what from how”(《Think in Java》3rd edtion),即多態提供了另外一種分離接口和實現(即把“做什么”與“怎么做”分開)的一種尺度。它是從設計的角度考慮的。
“The ability to use the same expression to denote different Operations is refered to as Polymorphism”,(《Object-Oriented Methods Principles & Practice》3rd Edition,第16頁)。簡單的說,多態就是“相同的表達式,不同的操作”,也可以說成“相同的命令,不同的操作”。這是從面向對象的語義的角度來看的。
三種說法分別從不同的角度來闡述了多態的實質。其中第三種說法尤為確切,下面著重分析第三種說法。
先解釋這句話的含義:
相同的表達式—函數調用
不同的操作  —根據不同的對象就有不同的操作。
舉個例子來說明,比如在公司中有各種職責不同的員工(程序員,業務員,文管等),他們“上班”時,做不同的事情(也可以看作是一種業務邏輯),我們把他們各自的工作都抽象為"上班",關系如下:
                      員工
                    /   |   /     ——繼承關系
              程序員 業務員 文管 
                            
每天上班時間一到,相當于發了一條這樣的命令:
  “員工們.開始上班”(同一條表達式)
每個員工接到這條命令(同樣的命令)后,就“開始上班”,但是他們做的是各自的工作,程序員就開始“Coding”,業務員就開始“聯系業務”,文管員就開始“整理文檔”。即“相同的表達式(函數調用),(在運行期根據不同的對象來執行)不同的操作”。
從語言實現多態的角度來說,多態是通過基類指針或引用指向派生類的對象,調用其虛方法實現的。下面是Object Pascal語言的實現
TEmployee=class    //把員工抽象為一個抽象類
  public
    procedure startWorking;virtual;abstract;
{抽象函數(即C++中純虛函數),什么也不做,實際的意義是,先預留一個接口。在其派生類中覆載實現它。}
  end;

  TProgramer=class(TEmployee)     //程序員
  public
    procedure startWorking;override;
  end;

  TBusinessMan=class(TEmployee)  //業務員
  public
    procedure startWorking;override;
  end;

  TDocManager=class(TEmployee)  //文管
  public
    procedure startWorking;override;
  end;
procedure TProgramer.startWorking;
begin
  showmessage('coding');
end;

{ TbusinessMan }

procedure TbusinessMan.startWorking;
begin
  showmessage('Linking Business');
end;

{ TDocManager }

procedure TDocManager.startWorking;
begin
  showmessage('Managing Document');
end;

procedure TForm1.Button1Click(Sender: TObject);
const
  eNum=3;
var
  Employee:array of TEmployee;
  i:integer;
begin
  setLength(Employee,eNum);
  Employee[0]:=TProgramer.Create; 
//把基類引用employee[0]指向剛創建的TProgramer對象
  Employee[1]:=TBusinessMan.Create;
  //把基類引用employee[1]指向剛創建的TBusinessMan對象
  Employee[2]:=TDocManager.Create;
    //把基類引用employee[2]指向剛創建的TDocManager對象
  for i:=0 to Length(Employee)-1 do
    Employee[i].startWorking; //在運行期根據實際的對象類型動態綁定相應的方法。
{從語言實現多態的角度來說,多態是通過基類指針或引用指向派生類的對象,調用其虛方法來實現的。Employee []為基類對象引用數組,其成員分別指向不同的派生類對象,當調用虛方法,就實現了多態}
end;
 試一試
大家可以敲入上面一些代碼(或Demo程序),并編譯運行,單擊按扭就可以看多態性的神奇效果了。

1.2多態的意義
封裝和繼承的意義是它們實現了代碼重用,而多態的意義在于,它實現了接口重用(同一的表達式),接口重用帶來的好處是程序更易于擴展,代碼重用更加方便,更具有靈活性,也就能真實地反映現實世界。
比如為了更好地管理,把程序員分為C++程序員,Delphi程序員。…
                      員工
                    /   |   /      ——繼承關系
              程序員 業務員 文管 
               /   /               ——繼承關系
      C++程序員  Delphi程序員
在程序員添加TCppProgramer,TDelphiProgramer兩個派生類后,調用的方式還是沒有變,還是“員工們.開始上班”,用Object Pascal來描述:

setLength(Employee,eNum+2);
Employee[ENum]:=TCppProgramer.create;
//創建一個TcppProgramer對象,并把基類引用employee[ENum]指向它
Employee[eNum+1]:=TDelphiProgramer.Create;

{員工們.開始上班}
for i:=0 to Length(Employee)-1 do
Employee[i].startWorking; //還是同一的調用方法(因為接口并沒變)。
     …
1.3多態在delphi中如何實現的?
實現多態的必要條件是繼承,虛方法,動態綁定(或滯后聯編),在Delphi是怎么實現多態的呢?
1.3.1 繼承(Inheritance)
繼承指類和類之間的“AKO(A Kind Of,是一種)”關系,如程序員“是一種”員工表示一種繼承關系。在Delphi中,只支持單繼承(不考慮由接口實現的多重繼承),這樣雖然沒有多繼承的那種靈活性,但給我們帶來了極大的好處,由此我們可以在任意出現基類對象的地方都可以用派生類對象來代替(反之不然),這也就是所謂的“多態置換原則”,我們就可以把派生類的對象的地址賦給基類的指針/引用,為實現多態提供了先決條件。
 提  示
在UML中:
AKO: A Kind Of 表示繼承(Inheritance)關系
APO: A Part Of 表示組合(Composition)關系
IsA: Is A表示對象和所屬類的關系
1.3.2 虛方法、動態方法與抽象方法,VMT/DMT,靜態綁定與動態綁定
對于所有的方法而言,在對象中是沒有任何蹤影的。其方法指針(入口地址)保存在類中,實際代碼則存儲在代碼段。對于靜態方法(非虛方法),在編譯時由編譯器直接根據對象的引用類型確定對象方法的入口地址,這就是所謂的靜態綁定;而對于虛方法由于它可能覆載了,在編譯時編譯器無法確定實際所屬的類,所以只有在運行期通過VMT表入口地址(即對象的首四個字節)確定方法的入口地址,這就是所謂的動態綁定(或滯后聯編)。
虛方法
虛方法,表示一種可以被覆載(Override)的方法,若沒有聲明為抽象方法,就要求在基類中提供一個默認實現。類中除存儲了自己虛方法指針,還存儲所有基類的虛方法指針。
聲明方法:
  procedure 方法名;virtual;
這樣,相當于告訴Delphi編譯器:
可以在派生類中進行覆載(Override)該方法,覆載(Override)后還是虛方法。
不要編譯期時確定方法的入口地址。而在運行期,通過動態綁定來確定方法的入口地址。
在基類中提供一個默認實現,如果派生類中沒有覆載(Override)該方法,就使用基類中的默認實現。
動態方法
動態方法和虛方法本質上是一樣的,與虛方法不同的是,動態方法在類中只存儲自身動態方法指針,因此虛擬方法比動態方法用的內存要多,但它執行得比較快。但這對用戶完全是透明的。
聲明方法:
  procedure 過程名;dynamic;
抽象方法
一種特殊的虛方法,在基類它不需提供默認實現,只是一個調用的接口用,相當于C++中的純虛函數。含有抽象方法的類,稱之為抽象類。
聲明方法:
  procedure 過程名;virtual;abstract;
VMT/DMT
在Delphi中,虛擬方法表(Virtual Method Table,VMT),其實在物理上本沒有,是為了更好地闡述多態,人為地在邏輯上給了它一個定義,實際上它只是類中的虛方法的地址的集合,這個集合中還包括其基類的的虛方法。在對象的首四個字節中存儲的“Vmt 入口地址”,實際上就是其所屬的類的地址(參考Demo程序)。有了實際的類,和方法名就可以找到虛方法地址了。
   Obj(對象名)        實際的對象                     所屬的類
 
Vmt 入口地址   
 數據成員
   
類虛方法表vmt入口地址   
數據成員模板信息   
靜態方法等   
虛方法(VMT)   
動態方法(DMT) 

 

 


圖3 對象名、對象與類的關系
DMT和VMT類似,也是邏輯上的一個概念,不同的是,在類中只保存了自身動態方法指針,而沒有基類的動態方法的地址,這樣就節省了一些內存,但速度不如虛方法,是一種犧牲時間換空間的策略,一般情況不推薦使用。
引用上面的例子來解釋一下:
Employee[i].startWorking;
Employee[i]是一個基類Temployee的對象引用,有上面的程序知道,它可能指向了一個Tprogramer對象,也可以可能指向一個TbusinessMan,還有可能是其他的對象,而且這些都是不確定的、動態的,所以在編譯時無法知道實際的對象,也就無法確定方法地址。而在運行期,當然知道對象的“廬山真面目”了,根據實際對象的首四個字節的內容,也就是虛擬方法表VMT的入口地址,找到實際要調用的函數,即實現了多態。
1.3.3 重載(Overload)與多態
很多網友認為函數重載也是一種多態,其實不然。對于“不同的操作”,重載無法提供同一的調用的方式,雖然函數名相同,但其參數不同!實現多態的前提,是相同的表達式!如Employee[i].startWoring,而重載的調用,有不同的參數或參數類型。重載只是一種語言機制,C語言中也有重載,但C語言沒有多態性,C語言也不是面向對象編程語言。除非重載函數同時還虛方法,不然編譯器就可以根據參數的類型就可以確定函數的入口地址了,還是靜態綁定!引用C++之父的話“不要犯傻,如果不是動態綁定,就不是多態”。
1.4多態種類的探討
1.4.1 兩級多態
對象級:用基類指針/引用指向其派生類對象,調用虛方法(或動態方法、抽象方法),這是用的最多一種。
類級:用類引用(指向類而不是對象的引用)指向派生類,調用虛類方法(或動態類方法、抽象類方法),常用在對象創建的多態性(因為構造方法是一種“特殊的”類方法,請參考我的另一篇拙作《剖析Delphi中的構造和析構》,第2.1節)。
提  示
類引用,是類本身的引用變量,而不是類,更不是對象引用。就和對象名表示對象引用一樣,類名就表示一個類引用,因為在Delphi中,類也是作為對象處理的。類引用類型就是類引用的類型,類引用類型的聲明方法:
類引用類型名稱=class of 類名
我們在VCL的源代碼中可以看到很多的類引用的聲明,如:
TClass=class of Tobject;
TComponentClass=class of Tcomponent;
TControlClass=class of Tcontrol;
 注  意 
在類方法中,方法中隱含的self,是一個類引用,而不是對象引用。
1.4.2 不安全的多態
  用派生類指針/引用指向基類對象也可以實現多態!雖然這是一種錯誤的使用方法:
procedure TForm1.btnBadPolyClick(Sender: TObject);
var
  cppProgramer:TCppProgramer;//定義一個cpp程序員引用,一個派生類的引用!
begin
   {*****************************聲  明***********************************
    用派生類指針/引用指向基類對象實現的多態。是一種病態的多態!
    這種多態的使用方法,它就象一個實際很小的事物(基類對象)披上一個強大
    的外表(派生類引用),因而帶來了許多潛在的不安全因素(如訪問異常),所
    以幾乎沒有任何價值。"杜撰"這樣一個例子,旨在說明在Delphi中的多態的本質,多態的本質:使用一個合法的(通常是基類的)指針/引用來操作對象,在運行期根據實際的對象,來執行不同的操作方法,或者更形象的說法:由對象自己來決定自己操作方式,編譯器只需下達做什么的命令(做什么what),而不要管怎么做(how),"怎么做"由為對象自己負責。這樣實現了接口和實現的分離,使接口重用變得可能。
   ***********************************************************************}

  cppProgramer:=TCppProgramer(TProgramer.Create);
   {為了實現這種病態的多態,把對象引用強制轉換為TCppProgramer類型,
    從而逃過編譯器的檢查}
  cppProgramer.startWorking;
   {調用的TProgramer.startWorking而不是TcppProgramer.startWorking
    這就是用派生類指針/引用指向基類對象實現的多態。}
  cppProgramer.Free;

  cppProgramer:=TCppProgramer(TDocManager.Create);
  cppProgramer.startWorking;
  {調用的竟然是TDocManager.startWorking,
   這就是用派生類指針/引用指向基類對象實現的多態。這種方法極不安全,
   而且沒有什么必要}
  cppProgramer.Free;
end;
 試一試
為獲得這種多態的感性認識,建議動手試試,上面說到這種使用方法會有潛在的不安全性(如訪問異常),而上面的程序運行一點錯誤都沒有出現,想想為什么?什么情況下會出現訪問異常,動手寫個訪問異常的例子,你將收獲更多。(參考Demo程序)
2 VCL中多態的應用
2.1構造與析構方法
構造方法的多態
由于構造方法可以看作“特殊的”類方法,在Tcomponent之后的所有的派生類的又被重新定義為虛類方法,因此要實現構造方法的多態性,就得使用類引用,在Delphi中有個經典的例子,就在每一個工程文件中都有一個類似下面的代碼:
application.CreateForm(TForm1, Form1);
其方法的定義:
procedure TApplication.CreateForm(InstanceClass: TComponentClass; var Reference);
var// InstanceClass為類引用。
  Instance: TComponent;
begin
  Instance := TComponent(InstanceClass.NewInstance);
{NewInstance方法的聲明:class function NewInstance: TObject; virtual; (system單元 432行)是一個類方法,同時也是虛方法,我們把它稱之為虛類方法。InstanceClass是一個類引用,實現了類一級的多態,從而實現了創建組件的接口重用}
  TComponent(Reference) := Instance;
  try
    Instance.Create(Self);//調用構造方法,進行初始化
  except
    TComponent(Reference):= nil;//消除“野“指針!good
    raise;
  end;
  {如果創建的是窗口且還沒有主窗體的話,就把剛創建的窗體設為主窗體}
  if (FMainForm = nil) and (Instance is TForm) then
  begin
    TForm(Instance).HandleNeeded;
    FMainForm := TForm(Instance);//設置主窗體
    { 實際上,在項目選項(project->options)中設置主窗體,實際上就把工程文件中相應的窗體語句提到所有創建窗體語句之前。}
  end;
end;
2) 析構方法的多態請參考《剖析Delphi中的構造和析構》,第3.3節
2.2 Tstrings
字符串數組處理在Delphi的控件中十分常見,通常是一些Items屬性,我們用起來也特別地方便(因為都是一樣的使用接口),這得益于Delphi中字符串數組的架構的設計。這是一個成功的設計。
由于很多控件中要用到字符串數組,如ComboBox,TstringGrid等等,但每個控件中的字符串數組又不同,Delphi由此把字符串數組但抽象出來,從而出現了很多與之相關的類。其中基類Tstrings只是提供為各種調用提供接口,具體實現完全可由其派生類中實現,因此,把Tstrings定義為抽象類。
下面就來看看基類TStrings類的常用方法的定義(參見Classes單元第442行):
  TStrings = class(TPersistent)
  protected
    ...
    function   Get(Index: Integer): string; virtual; abstract;
    procedure  Put(Index: Integer; const S: string); virtual;
    function   GetCount: Integer; virtual; abstract;
    …
  public
    function  Add(const S: string): Integer; virtual; //實際調用的是Insert
      {添加一字符串S到字符串列表末尾}
    procedure AddStrings(Strings: TStrings); virtual;
      {添加字符串列表Strings到該字符串列表末尾}
    procedure Insert(Index: Integer; const S: string); virtual; abstract;
      {抽象方法,在第Index位置插入一新字符串S}
    procedure Clear; virtual; abstract;
      {清除所有的字符串}
    procedure Delete(Index: Integer); virtual; abstract;
      {刪除某個位置上的字符串}
    function  IndexOf(const S: string): Integer; virtual;
      {獲取S在字符串列表中的位置}
    function  IndexOfName(const Name: string): Integer; virtual;
      {Returns the position of the first string with the form Name=Value with the specified name part}
    function IndexOfObject(AObject: TObject): Integer; virtual;
{獲取對象名為AObject:的對象在字符串列表中的位置}
    procedure LoadFromFile(const FileName: string); virtual;
      {Fills the list with the lines of text in a specified file}
    procedure LoadFromStream(Stream: TStream); virtual;
      {Fills the list with lines of text read from a stream}
    procedure SaveToStream(Stream: TStream); virtual;
      {Writes the value of the Text property to a stream object}
    property Strings[Index: Integer]: string read Get write Put; default;
      {References the strings in the list by their positions}
    property Values[const Name: string]: string read GetValue write SetValue;
{Represents the value part of a string associated with a given Name, on strings with the form Name=Value.}
    …
  end;
從Tstrings的定義可以看出,它的大部分Protected和Public的方法都是虛方法或是抽象方法。(請Soul來補充一些,TstringList->TstringGridString)
2.3其他(請soul來補充)
如果你對多態還不明白的話,那請你記住多態的實質:
“相同的表達式,不同的操作”(就這么簡單)
從OOP語言的實現來講,多態就是使用基類的指針/引用來操作(派生類)對象,在運行期根據實際的對象,來執行不同的操作方法;或者換一種更形象的說法:由對象自己來決定自己操作方式,編譯器只需下達做什么的命令(做什么what),而不要管怎么做(how),"怎么做"由為對象自己負責。這樣就實現了接口和實現的分離,使接口重用變得可能。
其實多態也簡單!那么使用多態應該注意什么呢?下面我的兩點幾點建議:
分析業務邏輯,然后把相關的事物抽象為“對象”,再用對象方法封裝業務邏輯。把一些具有多態性的操作,在基類中聲明為虛方法(virtual Method),對于在基類沒有必要實現的就聲明為抽象方法(virtual Abstract Method),然后在其派生類中再覆載它(Override),在使用的時候用基類的引用/指針來調用,這樣順理成章地實現了現實世界中的多態性。記住千萬不要為了多態,而去實現多態,這是一種走形式化的做法,是沒有意義的。
由于基類與派生類有一種天然“耦合”關系,修改基類就會導致“牽一發而動全身”,這將是非常麻煩的事情!因此要盡量弱化基類的功能實現,必要時把它設計為“抽象類”,并保證穩定的接口,這可以通過預留一些冗余的虛函數(或抽象函數)來實現。
相關問題
討論Delphi的多態: http://www.delphibbs.com/delphibbs/dispq.asp?lid=1753965
關于多態性: http://www.delphibbs.com/delphibbs/dispq.asp?lid=1854895
什么是多態?在日常編程中有哪些運用?http://www.delphibbs.com/delphibbs/dispq.asp?lid=960465
overload 與 override有何區別,請執教?http://www.delphibbs.com/delphibbs/dispq.asp?lid=296739
派生類的指針指向基類對象的問題 http://www.delphibbs.com/delphibbs/dispq.asp?lid=2104106
(最后一個問題是我在深入學習多態時在DelphiBBS上提的,曾引起熱烈的討論,建議看看)


上一篇:我的文章-《剖析Delphi中的構造和析構》

下一篇:一些讓我受益匪淺的delphi資源站點

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
學習交流
熱門圖片

新聞熱點

疑難解答

圖片精選

網友關注

主站蜘蛛池模板: 99sesese| 永久免费黄色片 | 91性高湖久久久久久久久网站 | 92自拍视频 | 成年免费视频黄网站在线观看 | 成人毛片100部 | 欧美日韩亚洲一区二区三区 | 成年片在线观看 | av电影在线免费观看 | 精品亚洲福利一区二区 | 草免费视频 | 狼伊千合综网中文 | 免费看性xxx高清视频自由 | 免费a级黄色片 | 成年人在线视频观看 | 黄色av网站免费看 | 国内精品一级毛片免费看 | 日本在线视频免费 | 成人国产精品一区 | 毛片a区 | 在线免费观看毛片视频 | 久久久久久亚洲综合影院红桃 | 久久久久一本一区二区青青蜜月 | 曰韩一二三区 | 久草在线视频中文 | 特大黑人videos与另类娇小 | 日韩黄在线 | 深夜激情视频 | 国产 日韩 一区 | 国产高潮失禁喷水爽到抽搐视频 | 久久精品视频亚洲 | 高清视频91 | 黄色高清免费 | 特级黄aaaaaaaaa毛片 | 国产1区视频 | 亚洲网在线观看 | 久久久一区二区三区精品 | 91精品免费观看 | 精品国产中文字幕 | 国产女同玩人妖 | 欧美大片一级毛片 |