Object Oriented Programming with Free Pascal and Lazarus/zh TW

From Free Pascal wiki
Jump to navigationJump to search

English (en) español (es) français (fr) magyar (hu) italiano (it) македонски (mk) русский (ru) 中文(中国大陆)‎ (zh_CN) 中文(台灣)‎ (zh_TW)

使用 Free Pascal 與 Lazarus 寫作物件導向程式

介紹

Pascal 有很多很好的教材,但這篇教學主要是讓初學者能更往前進到物件導向的程式設計,物件導向算是 Pascal 的延伸,從 Turbo-Pascal 提出,一直到 Delphpi,Free Pascal/Lazarus。

物件乃是標準 Pascal 的延伸 記錄結構。

文字基礎的 Pascal 程式撰寫能夠在像 Unix 下做成良好的應用程式,穩定地進行單一的工作。唯一一件設計師會把他寫的很複雜的事情,就像是需要做選項趨向的選單,但這基本上對於使用者想要下命令讓電腦,或印表機去照著做的空間還是很有限。

為了要提供 圖形化的使用者介面 (GUI),它通常都得必須和物件導向的程式設計方法脫離不了關係 (常常像是使用 C 或是他的延伸產品,或 Visual Basic,或其 物作導向衍生產品,也就等於Pascal 和 Free Pascal 相對於 Lazarus)。

在 GUI 裡使用者看到有一大堆的圖形有組織地排列在畫面上,分成數個工具群組分類放置,每種工具都可以做一些相關的動作像

  • 從選單選取項目,
  • 開啟或儲存檔案,
  • 連線到網路,
  • 進行數字運算,等等。

使用者可以移動滑鼠或其他指標裝置來選取畫面上的工具,利用點擊滑鼠左右鍵或按下鍵盤按鍵來執行一些動作。

當系統想要達成這樣的圖形使用者介面時,想要用標準的 Pascal 或是其他程式語言來撰寫,若導入物件導向語法就會變的很簡單,如所見的畫面上的每一個工具在程式裡都寫成一個物件,在共通統一的結構下擁有與自己相關的內容屬性,函式與程序。

物件 - 現實世界的例子

假想你在醫院或一個醫生的辦公室裡,想要分析收集來的血液樣本。

血液採樣

這些樣本的實體就算是一個物件,它有很多的訊息包含著或有其他的文件和物件與其有關。

  • 試管,即內科醫師用來進行檢驗的地方。
  • 內部流程 (或稱方法標準操作流程 (SOP)) 來指引護士或技師來採樣
    • 要用哪一種試管,
    • 如何進行採樣
    • 如何儲存樣本並送回實驗室。
  • 試管上的標籤訊息代表著
    • 採樣編號
    • 患者的姓名與生日
    • 採樣日期
    • 是否需要檢驗。
  • 在實驗室裡檢驗請求單會伴隨著樣本作業,指出
    • 採樣編號
    • 請求檢驗的醫師編號
    • 請求檢驗的醫師所要求的檢驗內容
    • 患者完整的資訊
    • 所希望看到的診斷確認。

請求單副本將放入患者的病歷表裡,去提醒師生在適當的時間內做出推斷出結果。

  • 在實驗室中 - 內部方法決定了
    • 樣本要如何來分析
    • 要用什麼樣的機械
    • 機械要如何去校正與操作
    • 結果要用什麼樣的格式寫下來,然後
    • 回報給醫生。

實際上結果即是醫生診斷後的記錄,然後一份副本留存在患者的病歷中。

樣本的實體也許還會留下來做參考用,以供未來確認或進一步再檢驗。或是通通棄置焚化掉;這也會有一些方法來專門處理這件事。

這個流程中醫師不需要在每一次要進行採樣時都要把詳細在重頭重述一遍;而且實際上醫師很可能只懂自己在實驗室裡要進行檢驗這部份的知識而已。而其他各種流程裡的智識是從之前的採樣與分析裡繼承下來的 - 這個流程會有一個通用旳操作準則,我們共稱做血液採樣,所有在這個方法之下它的文件與資料,用來組成這個物件

在醫師的心裡,血液採樣和它的結果被視為是同一件事(實體;entity),而對技師與護士而言,有關於試管,標籤和儲存方法,也都成為單獨的一件事(實體)。

另一個例子 - 汽車

如果你不喜歡血,同樣的我們可以舉另一個汽車拉進修車廠做修理的例子來說明。 修理過程可能包含:

  • 汽車實體
  • 擁有者所有的文件:任何證件 (包括車牌),保險,購買發票,配件,修理部位等
  • 油料耗用情形
  • 駕駛者的行照
  • 該修車廠的服務記錄
  • 整個維護與檢查行程的方法與程序
  • 例行行程之外的修理方法等
  • 客戶的付帳方法與資訊

程式範例

我想現實世界的舉例已經足夠!讓我們進入到我們真正的目的,撰寫物件化的 Pascal。

讓我們透過 Free Pascal/Lazarus 來建立一個有簡單的表單,帶幾個控制項的應用程式,說明以下狀況。

ObjectInspector-TForm.png
BlankForm.png

鑑於 Lazarus IDE,程式設計師從一份空白的表單樣版開始進行他的設計,想像設計師大膽地在表單上放入了很多的控制項與物件。

首先注意那張空白的表單已經是一個物件了,有它自己的內容,像是位置 (上基準點,左基準點),尺寸 (高與寬),顏色,加入文字時的預設字型等等。



假設一個按鈕控制項加入到表單裡 (type TButton),它也會有自己一系列的內容,可以在物件檢視器視窗中觀察到。

有幾個內容是跟空白表單是一樣的;這是因為很多內容都是繼承自上一個祖宗類別,它們已經原本就定義好它的子嗣物件要如何去處理這些內容。

同等於內容,物件檢視器裡還有一個頁籤叫事件,可以用來安排在事件處理事如像滑鼠鍵按一下按鈕 (OnClick) 或是像尺寸,位置等有了改變 (OnChange) 的時候,應用程式是要用來哪些方法或程序來進行。

在表單上那個按鈕實際的影像,就應該被認為是和他所有的內容,事件是同一體的,在 Pascal 下可稱為實體 (Entity) 或物件 (Object)。

ObjectInspector-TButton.png
FormWithButton.png
Source FormWithButton1.png

標準 Pascal 物件導向的擴充

Pascal 記錄結構透過以下定義來擴充

物件

物作算是一種特別的記錄。記錄的內容包括所有物件定義宣告的欄位(就像是所有一般的記錄),但現在程序和函式也可以被視為記錄的一部份,利用指標 (pointer) 指到原有物件型態下的方法來宣告。

舉例說明,一個物件可以包含有一個陣列,裡面帶有實際的值,然後透過方法來計算出它的平均。

Type
  Average = Object
    NumVal: Integer;
    Values: Array [1..200] of Real;
    Function Mean: Real; { 計算陣列的平均值 }
  End;

物件可以從「父」物件「繼承」欄位內與方法。這表示著,如果物件在宣告時加入是某某的「子」物件,也就可以使用同樣的欄位與方法。

更甚者,這裡還要導入一個可見度的概念:欄位,程序與函式都可以宣告成公開 (Public),保護 (Protected) 或私有 (Private) 三種,預設狀況欄位與方法都是公開的,可以匯出到外部。保護的欄位和方法在物件進行繼承的時候會從目前的祖宗物件傳給子嗣物件。而宣告成私有的欄位或方法,就只有目前自己的單元下可以存取:也就是它們的視界 (Scope) 被侷限於目前單元的實作裡面。

類別

Free Pascal 與 Lazarus 裡,物件並不是最常被用到那個部份;取而代之的類別才是廣泛的在使用,類別在宣告的步驟上和物件是相同的,但是它其實是個指標,指到物件,而不是物件本身。技術上層面來講,類別在程式裡的記憶體分派方式是堆積 (Heap;手動分配),而物件是用堆疊 (Stack;自動分配;空間有限)來分配。

這裡有一個簡單的典型類別宣告範例:

{-------------------------------------------}
{ 從 LCL 宣告類別的範例 }
{-------------------------------------------}
  TPen = class(TFPCustomPen)
  private
    FColor: TColor;
    FPenHandleCached: boolean;
    FReference: TWSPenReference;
    procedure FreeReference;
    function GetHandle: HPEN;
    function GetReference: TWSPenReference;
    procedure ReferenceNeeded;
    procedure SetHandle(const Value: HPEN);
  protected
    procedure DoAllocateResources; override;
    procedure DoDeAllocateResources; override;
    procedure DoCopyProps(From: TFPCanvasHelper); override;
    procedure SetColor
         (const NewColor: TColor; const NewFPColor: TFPColor); virtual;
    procedure SetFPColor(const AValue: TFPColor); override;
    procedure SetColor(Value: TColor);
    procedure SetMode(Value: TPenMode); override;
    procedure SetStyle(Value: TPenStyle); override;
    procedure SetWidth(value: Integer); override;
  public
    constructor Create; override;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
    property Handle: HPEN read GetHandle write SetHandle; deprecated;
    property Reference: TWSPenReference read GetReference;
  published
    property Color: TColor read FColor write SetColor default clBlack;
    property Mode default pmCopy;
    property Style default psSolid;
    property Width default 1;
  end;


請注意這個類別被宣告是另一個或稱祖宗類別 (TFPCustomPen) 的副本,也就是會繼承它所有的內容與方法。同時它也有屬於自己私有的欄位,被歸類於

  • 私有 - 在這裡定義的項目就代表它們只能被在同一個程式單元的其他類別或程序/函式所看到,利用 (有個例子是 Graphics,所以像其他的類別如 TBitMapTPicture 等等存在同一支單元就可以使用它)。它們基本上都算當地的變數 (如 FColorFPenHandleCached) 或就地使用的方法 (GetHandleSetHandle),但當變數保護公開區段宣告的也一樣可以被使用。
  • 保護 - 這裡的項目代表它們只能被繼承這個祖宗類別裡內容與方法的子嗣們所看到
  • 公開 - 這裡定義的項目代表他們在所有程式單元裡都可以看到,只要他在程式 Uses 語法的區段有 include 進來。
  • 發佈 - 這和公開public區的是一樣的,但編譯器同時會產生類型的資訊給自動串流的物件用。屬於發佈的欄位會列在物件檢視器上,若這裡沒有發佈的欄位,物件檢視器會以公開的欄位取代之。

方法

方法其實就像函式或程序,但它可以額外多一個指令 (directives) 的功能.

有些方法在宣告的時候下面就會標記著 virtual (虛擬)這個指令;或是某些標著override(覆蓋)這個指令。

  • virtual 代表這個副本的方法在編譯期還是未知的,但是等到程式的執行期如果該子程式被呼叫才會被選出來,也就是在定義的時候,這個宣告可以為此方法在類別預留空間。
  • override 這代表程式在執行期它從祖宗類別繼承下來的方法可以就地取代,尤其是對虛擬的方法。如果你想特別針對繼承下來的方法去使用,則你在呼叫的時候就需要用到inherited(繼承)這指令。

方法若即不下虛擬或覆蓋的指令,則它們屬於 static(靜態)的方法 (也是 Pascal 最一般的情況)。前兩者指令都算是動態的。

方法的特殊例子:

  • 建立 - 類別的創建子,用來負責記憶體的分配,收集所有所需要的資訊來設計/初始化所有類別裡的內容。
  • 銷毀 - 類別的解構子,有條理地將類別從系統中移掉,並將它本來佔有的資料還給系統做重新利用。

內容

內容就像是傳統 Pascal 記錄裡的欄位一樣,但它們都有讀取與/或寫入的特定項。

  • 讀取特定項是一個欄位,或說一個會為內容回傳正確型態的函式。像是在上面的例子裡,顏色 (Color) 這個內容會有一個叫 FColor 的讀取特定項,裡就有存有這個值備來使用。如果此特定項只有讀取,沒有寫入,那就代表此為唯讀的內容。
  • 寫入特定項是一個欄位,或說為用來儲存該值到指定的地方的程序。在上面的例子裡,顏色的寫入特定項 SetColor 即為一程序 (被定義為保護的),用來寫入顏色的值到指定的位置裡。如果一個內容只有寫入'而沒有讀取的指定對象,那就代表它是唯寫的。
  • 預設 - 請注意到可以為一個內容的值設定預設值。例如顏色的值在建立初我們預設值是 clBlack,或說黑色。未來在程式使用指派敘述句,或是直接在物件檢視器裡再指定其他顏色,就會替換掉預設值。
  • 索引特定項為是一個可以透過在會在內容間彼此共享的讀取寫入方法來辨視其可能為何值。請注意如果使用了索引,那麼該讀取寫入特定項就得要是一個函式或是程序,而不僅只是一個欄位/變數值。

範例

  TFooClass = class
  private
    FIntProp: Integer;
  public
    property IntProp: Integer read FIntProp write FIntProp;
  end;
  TFooClass = class  
  private
    function GetListProp(AIndex: Integer): String;
    property SetListProp(AIndex: Integer; AValue: String);
  public
    // 這個內容的型態不能放在類型裡'''發佈'''的宣告區段裡
    property ListProp[AIndex: Integer]: String read GetListProp write SetListProp; 
  end;
  TFooClass = class
  private
   function GetValue(const AIndex: Integer): Integer;
   procedure SetValue(const AIndex: Integer; AValue: Integer);
  public
    // 請注意到讀取和寫入的方法是共享的
    property Value1: Integer index 1 read GetValue write SetValue;
    property Value2: Integer index 2 read GetValue write SetValue;
    property Value3: Integer index 3 read GetValue write SetValue;
    property Value4: Integer index 4 read GetValue write SetValue;
    // 索引值可能是一常數,或隨便一個數字,或是一個組單選選項
    // 舉例而言:
    // property Value: Integer index ord(seSomeEnum) read SomeFunction write SomeProcedure;
  end;

Free Pascal 程式語言擴充

FPC 包含有幾個「標準」Pascal 語法的程式語言擴充,用來支持物件導向的程式撰寫。

這些擴充都有在 FPC 程式語言的參考指引網站: http://www.freepascal.org/docs.var 裡有特定的章節說明。 連結就在上面。程式語言參考指引裡包含程式語法示意圖與更多這裡的教學介紹沒有的教學。上面列出的四個特點,物件類別是構成 FPC 與 Lazarus 物件導向的基本形式。在物件的章節裡包含有更多觀念上的介紹,而類別的章節則減少與物件的重覆,把介紹重心放在與物件語法上的異同之處。普遍而言類別的實作其實在 Delphi 與 Lazarus 比較常被廣泛運用。而「物件」這個詞在 FPC 反而比較像在說明類別的概念上用的同意詞。這些文章同時也會詳細說明以降低對於專用詞彙的混淆。不過在此書的外界,「物件」這個字還是指為類別所建立出來的一個物件,事實上在 FPC 的執行期函式庫裡,所包括進,用來建立類別基礎內容的函式庫,就叫做TObject

如果是熟悉舊 Turbo Pascal 物件導向開發的使用者大概就可以跳過類別和物件的章節,因為整個物件語法都算是從 Turbo Pascal 衍生出來的語言。而對於 Delphi 的使用者,在類別的語法上也都是相似的,是以 Delphi 為基礎開發出來的。注意到類別章節的內容部份都是參考自物件的章節。對於麥金塔,熟悉各種蘋果電腦的開發者,THINK 和 MPW 的物件 Pascal 衍生語,無論是 FPC 的物件還是類別語法都沒有提供一個直接轉換的管道。在 2009 年的三月,在麥金塔的 Pascal 郵件討論社群裡有討論到能是否有可能開發出支援麥金塔 (使用新的語法) ,能存取蘋果電腦 Objective C / Cocoa 架構的編譯器。

物件導向 Pascal 的普遍概念

物件導向的程式寫作和其他的語言比較其特點與結構,乃提了多種去管理和封裝資料的方法,以管理程式的流程。物件導向程式常用於模組化圖形化的使用者介面 (GUI),或實體系統中讓程式感覺起來如同自然的事物。但物件導向程式也並不是適合所有的程式,程式物件程式沒有像使用程序結構的 Pascal 程式這麼明確易讀,想要看懂物件導向的程式得付出相對困難地翻閱一大堆的函式庫。學習做物件導向的分析有很多資源可以利用,在此說明文件之後未來還有很多程式設計與撰寫的技巧要學習。

有很多的程式語言都有物件導向的語法做為擴充。他們在敘述自己的物件導向概念時也許也會用到多種不同的術語來描述。甚至是在 FPC 裡有些術語其實是重疊的。一般而言,物件導向程式由一些物件的概念 (或稱資訊單元) 所組成,這些單元明確地將一些資料封裝或組合起來單獨負責處理這些資料。這些資料就可以在執行的時候保持不被干擾,也可以降低與全域變數宣告時發生混淆的問題。甚者,物件導向程式允許程式利用己定義好的物件做進一步功能上的擴充與修改,這個特徵術語稱呼叫繼承多型態。許多物件導向程式語言使用術語方法訊息來涉指物件裡的程序。而物件導向程式最厲害的地方就在於他可以在程式執行期動態地與主程式連結,而非編譯期。這種動態連結像是程序變數與參數在呼叫的時候才代入,但更具有語法依賴性上的優勢。封裝的概念也使得物件裡的方法可以進行所謂繼承的行為,此 wiki 下面將會開始做更多對物件導向的分析,設計與撰寫的說明。

更進一步的資訊

這裡所說的其實都只搔到表皮而已,對於更詳細的內容,強烈建議閱讀 Free Pascal 的使用者手冊,特別是第五章 (物件) 與第六章 (類別)

參見

外部連結