程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> 深入Delphi編程(OOP)

深入Delphi編程(OOP)

編輯:Delphi

剛剛接觸的Delphi的朋友,可能最感興趣的就是它豐富、強大的VCL(可視化構件庫)。僅僅向窗體上扔幾個構件,甚至不用動手寫代碼,就能很容易地做出一個有實用價值的程序,真是令人激動。但是,VCL只是Delphi的一小部分,Delphi的優秀遠遠不只是表現在VCL上。如果你僅僅停留在使用VCL這一階段,那麼你永遠也不可能成為一個真正的Delphi高手。記住,必須超越VCL,才能夠接觸到Delphi的核心。

那麼,在Delphi的VCL後面,到底隱藏著什麼呢?本文將討論兩個比較高級的Delphi主題:OOP和數據庫編程。

本文假定讀者已經具有Delphi編程的基礎知識,例如,熟悉Pascal語言的一般語法,掌握簡單的VCL使用,會使用Data-Ware構件編寫基本的數據庫程序,等等。本文不會重復VCL的使用這樣的問題。

1、OOP

OOP的英文全稱是Object Oriented Programming,翻譯過來就是面向對象編程。OOP是一種非常重要的編程思想。也許你會對這種抽象的東西不感興趣,可是幾乎任何一個高手都會告訴你:“語言並不重要,重要的是編程思想。”

大家知道,Delphi的語言基礎是Object Pascal。這是Borland在傳統的Pascal語言中增加了面向對象的特性後發展而成,並且特地冠以Object的字樣,以示與傳統的Pascal語言的差別,可見面向對象技術對其影響之大。可以說,Delphi構建在Object Pascal的基礎之上,而Object Pascal構建在面向對象技術之上。

事實上,不但Delphi,OOP也是C++、Java等其他許多現代編程語言的基礎(Visual Basic不完全地支持OOP)。熟練地掌握OOP技術,是深入掌握Delphi的必要條件,是邁入高手境界的必經之路,也是一個成熟的程序員的標志之一。理解了OOP技術之後,很多以前令你困惑的東西會迎刃而解。

有趣的是,雖然Delphi是完全基於OOP的,但是一個完全不了解OOP的程序員也能夠使用Delphi編寫程序,因為Delphi會自動完成絕大多數的工作。當你開始學習Delphi的時候,你可能無法想象,當簡單地往窗體上添加一個按鈕時,Delphi會完成多麼復雜的工作吧!但是既然有志於深入Delphi的世界,成為一個真正的程序員,我們就應該對Delphi的細節具有好奇心。

這些理論可能會讓人覺得枯燥和望而生畏,其實當你掌握了它之後就會覺得沒什麼了。當然,你需要有足夠的毅力。

OOP有三個主要的特征:

1.1 數據封裝

讓我們先看一段代碼:

  type
  TDate = class
  Mouth,day,Year:Integer;
  procedure SetValue(m,d,y:Integer);
  function LeapYear:Boolean;
  end;

我們首先會看到class關鍵字,它的中文譯名為“類”。類是一個非常重要的概念。根據權威的定義,類是:一種用戶定義的數據類型,它具有自己的說明和一些操作。一個類中含有一些內部數據和一些過程或函數形式的對象方法,通常來描述一些非常相似的對象所具有的共同特征和行為。

這個定義可能比較晦澀。你可以把類想象為一種特殊的Record類型,其中不但可能包含數據,而且可能包含函數和過程(在OOP中稱之為方法)。這些數據和方法被統稱為類的成員。

上面這個類很顯然是一個日期類型,它包括Mouth,Day,Year這三個數據成員,和SetValue、LeapYear這兩個方法。順便說一句,在Delphi中,習慣以字母T作為每個類的前綴,就象Viusal C++中習慣以字母C作為每個類的前綴一樣。

Mouth,Day,Year這三個數據成員指定了該日期的年、月、日。SetValue方法為這三個數據成員賦值,而LeapYear檢查當前日期所在的那一年是否是閏年。下面我們給出這兩個方法的實現部分:

  procedure TDate.SetValue(m,d,y:Integer);
  begin
  Mouth := m;
  Day := d;
  Year := y;
  end;  function TDate.LeapYear:Boolean;
  begin
  if (Year mod 4 <> 0) then
  LeapYear := False
  else if (Year mod 100 <> 0)
  LeapYear := True
  else if (Year mod 400 <> 0)
  LeapYear := False
  else
  LeapYear := True;
  end;

實現了這些定義之後,就可以這樣調用它們:

  var
  ADay: TDate;
  begin
  //建立一個對象
  ADay := TDate.create;
  //使用之
  ADay.SetValue(1,1,2000);
  if ADay.LeapYear then
  ShowMessage('閏年:' + Inttostr(ADay.year));
  //釋放對象
  ADay.free;
  end;

我們來逐行解釋這些代碼的含義。var後面那一行聲明了一個TDate類的變量。

聲明了變量之後,我們怎麼使用它呢?使用TDate類的Create方法可以建立一個該類的對象,並將其賦予ADay變量。

現在我們又接觸到了OOP中另一個重要的概念:對象。什麼是對象?簡言之,對象就是類的實例,或者說,是類定義的數據類型的變量。當建立一個類的對象時,系統為它分配一塊內存。例如我們定義一個變量A為Integer類型,那麼,Integer是一個數據類型,A就是一個實例。類與對象的關系就類似於這兩者之間的關系。區別類和對象是非常重要的,甚至一些專業的程序員都往往將他們搞混。

細心的讀者可能注意到,在TDate類的定義中,並沒有Create這個方法。那麼這個Create方法是從哪裡來的呢?Create方法是每一個Class都具有隱含的方法,它的作用是建立這個類的實例。請注意,在這裡,類和其他的數據類型是不同的。其他的數據類型都是聲明了變量之後就可以直接使用,而類類型必須在使用Create方法創建它的實例(對象)之後才能使用。

事實上,在C++和其他大多數的OOP語言中,聲明一個類的變量就能夠同時建立起這個類的對象。而Delphi(包括它的孿生兄弟C++ Builder)在這方面與眾不同,必須要Create一下才能真正建立對象。同時,在這個對象不再需要時,必須要手工調用free方法釋放這個對象(當然,free方法也是每個類隱含的)。這和Delphi獨特的“對象引用模型”有關,有興趣的朋友可以查閱有關資料,我就不多說了。

這種情況造成了一個非常有趣的現象,那就是,編程的初學者往往忘記在使用對象之前create它,從而出錯,但從C++轉向Delphi的高手也常常犯同樣的錯誤……

順便告訴大家一個訣竅,當編譯器出現“Read of Address: ffffffff”這樣的錯誤時,多半是因為在使用對象之前忘了Create,可以從這方面入手檢查代碼。另外,也千萬不要忘記在不需要它時使用free釋放掉,否則可能造成內存洩漏。

在建立和釋放對象的代碼的中間,是使用對象的代碼。訪問對象的數據成員非常簡單,和Record類型沒有什麼區別。可以點號表達式來訪問它們:

  ADay.Year := 2000;
  ADay.Mouth := 1;
  ADay.Day := 1;

同樣,也可以使用點號表達式來調用對象的方法。如果你閱讀了方法實現部分的代碼,你可以很容易地發現,ADay.SetValue(1,1,2000)這一句分別為三個數據成員賦了值,而ADay.LeapYear調用則返回當前日期所在年是否為閏年。至此,整段代碼的意義也就清楚了。

然而,類不僅僅這麼簡單。上面這個例子是一個非常簡單的類,可以直接訪問它的任何成員(數據和方法)。但某些類的成員是不能被隨便訪問的。Delphi中用三個關鍵字區分這些成員的訪問權限:

表1

Private 該類型的成員只能在聲明類中被訪問

Public 該類型的成員可以被程序中的任何地方的代碼訪問

Protected 該類型的成員只能在聲明類以及聲明類的派生類中被訪問

Protected類型的成員以及什麼是“派生類”等問題我們留到以後再進行討論,現在我們將注意力集中在前兩者。

Public類型就是在上面例子中的那種類型,這個很好理解。而Private類型,根據表格中的簡單解釋,只能在該成員被聲明的那個類(也就是該成員所屬的那個類啦)中被訪問,越出這個界限,它就是不可見的。那麼,Private類型的成員將如何被使用呢?簡單地說,就是通過一個Public類的方法來訪問它。

讓我們看一個新的例子:

  type
  TDate = class
  private
  Mouth,day,Year:Integer;
  Public
  procedure SetValue(m,d,y:Integer);
  function LeapYear:Boolean;
  function GetText:String;
  end;

在這個類中,Mouth,Day,Year這三個成員被聲明為Private成員,因此它們在類以外的其它地方是不可訪問的。也就是說,如果你使用

  ADay.Year := 2000;

這樣的代碼,那麼編譯器將會報錯。但是,我們可以照樣通過SetValue方法為它們賦值:

  ADay.SetValue(1,1,2000);

這行代碼是合法的,因為SetValue本身是TDate類的成員,而且它又是一個Public成員。而使用GetText方法則可以得到當前日期值(這也是得到當期日期值的唯一辦法)。

這樣的設置使得類的一些成員被隱含起來,用戶只能用一些專門的方法來使用它們。那些可以被外部代碼訪問的成員稱之為類的接口。這樣做有什麼好處呢?首先,這讓類的作者可以檢測被賦值的內容。比如,用戶可能給一個對象賦予13月40日這樣的無效日期。而在隱含了一些成員之後,類的作者可以在方法的代碼中檢測這些值是否有效,從而大大地減少了產生錯誤的機會。其次,使用規范的類,作者可以隨時修改類內部的代碼,而使用該類的代碼卻無需任何修改!這樣使得代碼的維護成了一件輕松的事件,特別是對於多人協作的大型軟件而言。

這就叫做數據的封裝(encapsulation)。這是OOP的第一個特征。一個優秀的OOP程序員,應該在設計類的時候,就確定將哪些重要的數據封裝起來,並給出一個高效率的接口。

需要指出的一點是,表1中Private部分的論述對於“標准的”OOP語言(例如C++)是完全正確的,但對於Delphi有一個例外。在Delphi中,Private成員除了在聲明類中可以訪問外,在聲明類所在的單元(.pas文件)中的任何地方都能被訪問,不論這些代碼與聲明類的關系如何。嚴格來說,這是違反OOP的原則的,我不明白Borland為何要這麼做(據說是為了方便)。在關於Delphi的優劣性的討論中,這是常被涉及的一個問題。

1.2 繼承與派生

我們再來看一段代碼:

  type
  TNewDate = class(TDate)
  Public
  function GetTextNew:String;
  end;  function GetText:String;
  begin
  return := inttostr(Mouth) + ':' + inttostr(Day) + ':' + inttostr(Year);
  end;

可以看到,在class後面出現一個包含在括號中的類名。這種語法表示新的類繼承了一個舊的類。繼承了原有類的類稱之為派生類,也叫子類,被繼承的類稱之為基類,也叫父類。

派生類與基類之間是什麼關系呢?當派生類繼承自一個基類時,它自動具有基類的所有數據、方法以及其他類型,無須在派生類中再做說明。例如,可以象下面這段代碼這樣使用TNewDate類:

  var
  ADay: TNewDate;
  begin
  ADay := TNewDate.create;
  ADay.SetValue(1,1,2000);
  if ADay.LeapYear then
  ShowMessage('閏年:' + Inttostr(ADay.year));
  ADay.free;
  end;

而且,派生類還可以在基類的基礎上加入自己的數據和方法。可以看到在TnewDate類中增加了一個新的方法GetTextNew。下面給出這個方法的實現部分:

  function GetTextNew:String;
  begin
  return := GetText;
  end;

然後調用它:

ADay.GetTextNew;

這個新的方法工作得很好。

為什麼GetTextNew方法必須調用基類中的GetText方法,而不能直接使用GetText方法中的那些代碼呢?原因是,Mouth,Day,Year這三個成員被聲明為Private成員,因此它們即使在派生類中也是不能被訪問的,所以必須調用基類中的GetText方法,間接地使用它們。如果要直接使用它們的話,可以將這三個成員的屬性從Private改為Protected。在表1中可以看到,Protected屬性的成員可以在聲明類以及聲明類的派生類中被訪問,然而仍然不能被這兩種情況以外的其他代碼所訪問。現在我們終於可以理解了,這個特殊的屬性實際上提供了極大的方便:它使得類的成員被封裝,避免了混亂,同時又能夠讓派生類方便地使用它們。

(如果你是一個細心的人,你可能發現上面的話中間有一個小小的仳漏。當你真的在GetTextNew方法中訪問了基類的Private成員的話,你可能會驚奇地發現程序也能夠編譯通過而且正常運行!其實,這個問題和OOP本身沒有關系。上面我已經說過,在Delphi中,Private成員在聲明類所在的單元文件中的任何地方都能被訪問,因此如果TNewDate類和TDate類在同一個.pas文件中時,這種情況就不足為怪了。)

怎麼樣,是不是覺得非常奇妙?通過這種繼承的機制,類不再僅僅是數據和方法的封裝,它提供了開放性。你可以方便地繼承一個功能強大的類,然後添加進自己需要的特性,同時,你又不需要對基類進行任何的修改。相反,原作者對基類的任何改動,都可以在你的新類中立即反映出來。這非常符合代碼的重用要求。

這種繼承機制也非常符合現實世界中的情形。可以設想,一般意義上的“動物”是一個類,具有自己的一些特征(成員);而“狗”是“動物”的派生類,它具有動物的所有特征,同時還具有自己獨有的特征(四條腿,汪汪叫,等等)。而“狗”這個類可以繼續派生下去,例如“黑狗”“白狗”,它們除了保留狗的全部特征之外,還具有自己的特征(黑顏色,白顏色,等等)。而具體到一只活生生的狗,可以認為它就是“黑狗”或“白狗”(或其他什麼狗)的一個實例(對象)。

OOP這種對現實世界的模擬不僅極大地簡化了代碼的維護,而且使得整個編程思想產生了革命性的變化,較之模塊化編程有了飛躍的進步。

如果你曾經仔細閱讀過VCL的資料甚至它的源代碼,你就可以發現,整個VCL都是建立在這種強大的封裝-繼承的機制之上的。你可以看到一張詳細的VCL層次結構圖,就象是一個龐大的家譜,各種VCL構件通過層層繼承而產生。例如,一個簡簡單單的TForm類,就是許多次繼承之後的產物:

TObject - TPersistent - TConponent - TControl - TWinControl - TScrollingWinControl - TCustomForm - TForm

不但Delphi的VCL,Visual C++中的著名的MFC(Microsoft Foundation Class,微軟基本類庫),以及以前Borland C++中風光一時的OWL(Object Window Library,對象窗口類庫),都是建立在這種機制之上。所不同的是,對於前兩種語言,你要花上好幾個月的功夫去基本掌握那些繁復無比的類,才能寫出比較有實用價值的程序,而在Delphi中,大部分的工作Delphi都已經自動幫你完成了。例如,每次你向程序中加入一個窗體時,Delphi就自動為你從TForm派生一個新類(默認為TForm1),並且為這個新類創造一個實例。你對這個窗體的改動(添加構件和代碼之類),無非是為這個派生類加入一些新的特性而已;你再也用不著自己去處理最大化、最小化、改變大小這一類的情況,因為這些代碼都在基類中被實現,而被派生類所繼承了。這就是Delphi的偉大之處。當然,Delphi的VCL也絕不比MFC或OWL遜色(事實上它是由後者演變而來)。

(可能有人會問起VB的情況。VB不支持繼承,因此並沒有什麼復雜的類庫,它自己的控件也少得可憐,主要是使用ActiveX控件。)。

也許你已經若有所悟,為你的發現而心癢難騷了吧。但是,我們要討論的東西當然不會僅僅這麼簡單。

在1.1部分(“數據封裝”),我們講到了“Create方法是每一個Class都具有隱含的方法”。其實,這種說法是不准確的。事實是,在Delphi中,所有的類都默認繼承自一個最基礎的類TOject,甚至在你並未指定繼承的類名也是如此。Create方法是TObject類具有的方法,因此理所當然,所有的類都自動獲得了Create方法,不管你是否實現過它。想想看就知道了:如果沒有Create方法的話,怎樣建立一個對象呢?

你可能注意到了Create方法是一個特殊的方法。不錯,Create方法的確非常特殊,甚至於它的“頭銜”不再是function或procedure,而是Constructor(構造器)。你可以在VCL的源碼中見到這樣一些例子:

Constructor Create;

構造器不僅是一個Delphi關鍵字,而且是一個OOP方法學的名詞。與之相對應的,還有Destructor(毀壞器)。前者負責完成創建一個對象的工作,為它分配內存,後者負責釋放這個對象,回收它的內存。要注意的一點是,Constructor的名字一般是Create,但Destructor的名字卻不是Free,而是Destroy。例如:

Destructor Destroy;

那麼,在以前的代碼,為什麼又使用Free來釋放對象呢?二者的區別是,Destroy會直接釋放對象,而Free會事實檢查該對象是否存在,如果對象存在,或者對象不為nil,它才會調用Destroy。因此,程序中應該盡量使用free來釋放對象,這樣更加安全一些。(但要注意,free也不會自動將對象置為nil,所以在調用free之後,最好是再手動將對象置為nil。)

象對待一般的函數或過程那樣,也可以向構造器傳遞參數:

  type
  TDate = class
  private
  Mouth,day,Year:Integer;
  Public
  function LeapYear:Boolean;
  function GetText:String;
  Constructor Create(m,d,y:Integer);
  end;  procedure TDate.Create(m,d,y):Integer;
  begin
  Mouth := m;
  Day := d;
  Year := y;
  end;

調用它:

  ADay: TDate;
  begin
  ADay := TDate.create(1,1,2000);
  if ADay.LeapYear then
  ShowMessage('閏年:' + Inttostr(ADay.year));
  ADay.free;
  end;

這樣,在Create方法裡就完成了對數據的初始化,而無須再調用SetValue方法了。

接下來,我們將要涉及到另一個重要的、也是很有趣的問題:方法的虛擬與重載。

可能你已經有點暈了吧……還是先看一個新的例子:

  type
  TMyClass = class
  procedure One;virtual;
  end;  type
  TNewClass = class(TMyClass)
  procedure One;override;
  end;  procedure TMyclass.One;virtual;
  begin
  ShowMessage('調用了TMyclass的方法!');
  end;  procedure TNewClass.One; override;
  begin
  Inherited;
  ShowMessage('調用了TNewClass的方法!');
  end;

可以看到,從TMyClass派生了一個新類TNewClass。這兩個類都聲明了一個名字相同的方法One。所不同的是,在TMyClass中,One方法後面多了一個Virtual關鍵字,表示這個方法是一個虛擬方法(Virtual Method)。而在TNewClass中,One方法後面多了一個Override關鍵字,表示該方法進行了重載(Override)。重載技術能夠實現許多特殊的功能。

讓我們來仔細分析它們的實現部分。在TMyclass.One方法的實現部分,調用ShowMessage過程彈出一個對話框,說明該方法已被調用;這裡沒有任何特別的地方。在TNewClass.One方法中,出現了一條以前從未出現過的語句:

Inherited;

這個詞的中文意思是“繼承”。我們暫時不要去涉及到太過復雜的OOP概念,只要知道這條語句的功能就是了。它的功能是調用基類中相當的虛擬方法中的代碼。例如,你如果使用以下代碼:

  var
  AObject: TNewClass;
  begin
  AObject := TNewClass.create;
  AObject.One;
  AObject.free;
  end;

那麼程序將彈出兩次對話框,第一次是調用TMyclass類中的One方法,第二次才是TNewClass.One方法中的代碼。

重載技術使得我們不但可以在派生類中添加基類沒有的數據和方法,而且可以非常方便地繼承基類中原有方法的代碼,只需要簡單地加入Inherited就可以了。如果你不加入Inherited語句,那麼基類的相應方法將被新的方法覆蓋掉。但是必須注意,重載只有在基類的方法被標志為Virtual時才能進行,而且重載的方法必須具有和虛擬方法完全相同的參數類型。

虛擬方法還有一種特例,即抽象方法:

procedure One;override;abstract;

在One方法後面,不但有override關鍵字,還多了一個abstract關鍵字(意為抽象)。這種方法稱為抽象方法(在C++中稱為純虛擬函數)。含有抽象方法的類稱為抽象類。抽象方法的獨特之處在於,它只有聲明,而根本沒有實現部分,如果你企圖調用一個對象的抽象方法,你將得到一個異常。只有當這個類的派生類重載並實現了該方法之後,它才能夠被調用。(在C++中,甚至根本就不能建立一個抽象類的實例。)

既然如此,那麼這種抽象方法又有什麼用呢?這個問題我們將在接下來的“多態”部分進行討論。

1.3 多態

多態相對來說比較復雜一點。不過不要擔心,它的內容比較少,而且如果以前的知識掌握得比較穩固的話,多態的概念是水到渠成的。

先來討論一下類型的兼容性問題。下面是一個例子:

  type
  TAnimal = Class
  Procedure Voice;virtual;
  ...
  end;  TDog = Class(TAnimal)
  Procedure Voice;Override;
  ...
  end;  implementation  Procedure TAnimal.Voice;virtual;
  Begin
  PlaySound('Anim.wav',0,snd_Async);
  End;  Procedure TDog.Voice;virtual;
  Begin
  PlaySound('Dog.wav',0,snd_Async);
  End;

TDog類繼承了TAnimal類,並重載了其中的Voice方法。PlaySound是一個WIN API函數,可以播放指定的wav文件。(這個函數的定義在MMSystem.pas文件中可以找到。)

先看這段代碼:

  var
  MyAnimal1, MyAnimal2: TAnimal;
  Begin
  MyAnimal1 := TAnimal.Create;
  MyAnimal2 := TDog.Create;
  ...

在實現部分的第一行中,建立了一個TAnimal類型的對象,並將其賦予TAnimal類型的變量MyAnimal1。這是很正常的事。但在第二行中,建立了一個TDog類型的對象,並將其賦予了TAnimal類型的變量MyAnimal2。這看上去令人吃驚,但這些代碼是完全合法的。

眾所周知,Pascal以及Object Pascal是一種類型定義嚴格的語言,你不能將某個類型的值賦予不同類型的變量,例如將一個整型值賦予布爾型變量,將會導致出錯。但是,這個規則在涉及到OOP領域時,出現了一個重要的例外,那就是:可以將一個子類的值賦予一個父類類型的變量。但倒過來卻是不行的,一個父類的值決不能賦予一個子類類型的變量。

如果將這個原則放到現實世界中,那就很容易理解了:“狗”繼承自“動物”,因為狗也是一種動物。所以可以將一個“狗”類型的值賦予“動物”類型的變量,因為“狗”具有“動物”的一切特征。但反過來,“動物”不具有“狗”的所有特征,因此反向賦值是不行的。

那麼,這種兼容規則在編程中究竟有什麼用處呢?

請注意下面這段代碼:

  var
  MyAnimal1, MyAnimal2: TAnimal;
  Begin
  MyAnimal1 := TAnimal.Create;
  MyAnimal2 := TDog.Create;
  MyAnimal1.Sound;
  MyAnimal2.Sound;
  ...

MyAnimal1和MyAnimal2都是TAnimal的變量,而且都調用了Sound方法。但是,執行的結果是完全不同的:前者執行的是TAnimal.Voice的代碼,而後者執行的是TDog.Voice的代碼!其原因很簡單,因為MyAnimal1被賦予了TAnimal類型的對象,而MyAnimal2被賦予了TDog類型的對象。也就是說,一個TAnimal類型的變量,當它調用Sound方法時,所執行的代碼是不確定的:可能執行TAnimal.Voice的代碼,也可能執行的是TDog.Voice的代碼,取決於它當時引用的是一個什麼樣的對象。

再看:

  MyAnimal1 := TAnimal.Create;
  MyAnimal1.Sound;
  MyAnimal1.free;
  MyAnimal1 := TDog.Create;
  MyAnimal1.Sound;
  ...

同一個變量MyAnimal1,在第一次調用Sound方法時,執行的是TAnimal.Voice的代碼,在第二次時執行的是TDog.Voice的代碼。MyAnimal1.Sound這行代碼不需要變化,程序可以根據不同的情況賦予該變量不同的對象,從而使它執行不同的代碼。這就是多態的定義。

這個非常重要的特點大大地增加了代碼的可復用性。如前所述,只需要簡單地寫下一行代碼,就可以讓程序執行不同的功能,因為這個虛擬方法同TAnimal的任何派生類都是兼容的,甚至連那些還沒有編寫出來的類也是一樣。而程序員並不需要了解這些派生類的細節。利用多態性寫出來代碼,還具有簡潔和維護性好的特點。

現在我們可以回到本文的1.2節結尾處的問題了。抽象方法本身不能夠做任何事情,必須在子類中被重載並實現,才能夠完成有意義的工作。但抽象方法的存在,相當於為父類留下了一個接口,當程序將一個子類的對象賦予父類的變量時,父類的變量就可以調用這個方法,當然此時它運行的是相應的子類中重載該方法的代碼。如果沒有這個抽象方法,父類的變量就不能調用它,因為它不能調用一個只在子類中存在、而在父類中不存在的方法!

關於OOP的介紹就到此這止。在以上這些篇幅裡,介紹的只是OOP最基本的一些概念,讓讀者對OOP有一定的系統認識,也為下文的討論打好基礎 。更多、更深入的東西等待著你自己去發掘。

本文已經多次強調OOP的重要性,這裡還要強調一次:對OOP的掌握程度,在某種意義上決定著你對Delphi世界的理解能力。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved