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

三談多態——善用virtual

編輯:Delphi
  多態性,是一種能給程序帶來靈活性的東西。看過《設計模式》的程序員應該都知道,相當多的模式(幾乎所有)都是依靠多態來實現的,以此給程序提供可擴展、可重用性。在《再談多態——向上映射及VMT/DMT》一文中,提到了多態性是依賴於虛函數/虛方法(即動態綁定)來實現的,也介紹了虛函數/虛方法(virtual)的實現方法。那麼本文就來談一下,如何使用virtual、善用virtual來獲取多態性給我們帶來的靈活性。
  
  實例是最好的教材,因此本文還是假設一個需求,寫一個實例來講解。不過,我想沒有必要給出所有源碼,因此在本文中有些實現的代碼會粗略帶過。另外,本文所有代碼均為Object Pascal語言編寫,實現環境為Delphi。
  
  另外,由於“方法(Method)”一詞已經成為Object Pascal的術語,因此,以下稱成員函數都為“方法”。也許C++程序員會不太適應這樣的稱呼(呵呵,我自己也不太適應),見諒吧。
  
  假設我們要編寫一個純文本內的編輯器,也就是記事本(呵呵,別嫌例子老套,記事本程序在相當多方面都是很好的教材),編輯控件我們一般會用TMemo或TRichEdit,但是它們的功能都不甚強大,也許我們目前沒有,但日後會找到一個更好的第三方文本編輯控件(比如,支持語法著色的)。因此,我們必須為未來的改進留下方便之門,否則到時候再重寫全部程序真是太傻了。
  
  界面層(菜單響應、狀態顯示等)對文本編輯器控件的控制的代碼對於所有編輯器來說都是類似的,應該可以被重用。那麼就必須將界面層的代碼與編輯器控件的控制代碼隔離開來。
  
  如何隔離?我們可以為所有的編輯器控件指定一個公共的接口(抽象類),界面層只看得到這個接口,只使用接口提供的功能,那麼,我們更換任何編輯器控件時,都不必更改界面層的代碼了。
  
  首先,抽象出編輯器的基本操作,如:Load(打開文本)、Save(保存到文件)、Copy(復制到剪貼板)等等,將這些操作作為public方法。其次,考慮這些操作中有哪些會涉及到具體相關控件的,對於這些操作,你有兩種選擇:1、如果完全依賴控件本身的,可以選擇將其定義為虛方法或抽象虛方法(即C++中的純虛函數);2、如果只是有部分代碼依賴控件本身的,將這部分操作抽象到一個protected的抽象虛方法中,而將相同的部分代碼寫在基類中,並由基類的方法中調用protected的抽象虛方法。
  
  如果還沒有完全明白,請看代碼:
     
      TEditor = class // 抽象基類
  private
      m_FileName : String;
  protected
      function DoLoad(FileName : String) : Boolean; virtual; abstract;
      public
          function Load(FileName : String) : Boolean;
          function Save() : Boolean;
          function SaveAs(FileName : String) : Boolean; virtual; abstract;
      // ... 其他需要的操作,由需求而定
      end;
  
  好,我們來詳細說明一下TEditor為什麼是這個樣子的。其有一個私有成員,保存編輯器所對應的文件名。然後看public部分,它至少提供了三個操作:Load——從某文件中讀取文本到編輯器;Save——將文本保存到文件,文件名為m_FileName所保存的;SaveAs——以指定的一個文件名保存文本。

  三個操作中,SaveAs為抽象虛方法,因為它的實際動作與每個編輯控件相關,而基類本身並不知道該如何保存文件。

  Save和Load都是非虛方法,其中Save的任務很簡單,就是將文本以m_FileName所保存的文件名保存,其實現可以是調用抽象的SaveAs,只需要將文件名傳給SaveAs即可。基類完全可以確定如何完成Save(因為它可以保證派生類實現了SaveAs),因此其為非虛方法。

  而Load呢?Load操作的步驟是:1、將文本從文件中讀取到編輯器控件中;2、將m_FileName的值設置為所讀取的文件名。其中第一個步驟與具體編輯器控件相關,應該是由派生類去實現它,第二個步驟派生類無法實現,因為派生類看不到私有的m_FileName成員。但即使把m_FileName移到protected節中,以使派生類可以訪問它,也並非好辦法,因為這樣的話,在實現每個派生類時,都要記住設置m_FileName的值,這不僅造成代碼重復(每個派生類都要這樣做),而且這不應該是派生類的義務。因為m_FileName應該是基類的內部實現,對外不可見。因此,第二個步驟應該由基類來完成。如何達成這個目的呢?

  我們可以注意到,protected節中有一個DoLoad方法,它就被用來完成第一個步驟——每個編輯器控件去將文本讀入編輯器。然後,DoLoad由Load方法中被選擇在適當的時機調用。
  
      基類的實現如下:

      function TEditor.Load(FileName : String) : Boolean;
  begin
      Result := DoLoad(FileName);
      if Result then
          m_FileName := FileName;
      end;
  
      function TEditor.Save() : Boolean;
  begin
      SaveAs(m_FileName); // 調用抽象的 SaveAs
      end;
  
  接著,我們使用TMemo來實現一個編輯器類:
  
  TMemoEditor = class(TEditor)
  private
      m_Editor : TMemo;
  protected
      function DoLoad(FileName : String) : Boolean; override;
      public
      constructor Create();
      destrcutor Destroy(); override;
  
      function SaveAs(FileName : String) : Boolean; override;
      // ...其它需要的操作
  end;
  
  在該派生類中,有一個私有成員,即TMemo控件的實例。然後覆蓋(override)了基類的兩個抽象虛方法:DoLoad和Save。
  
  其實現如下:
  function TMemoEditor.Create();
  begin
      // 創建TMemo實例
          m_Editor := TMemo.Create(nil);
     
          // 接著完成將TMemo實例置於界面上顯示出來等操作,省略
      end;
  
  function TMemoEditor.Destroy();
  begin
      // 其他清理工作
  
      m_Editor.Free();
      m_Editor := nil;
  end;
  
  function TMemoEditor.DoLoad(FileName : String) : Boolean;
  begin
          Result := false;
          try
            m_Editor.LoadFromFile(FileName);
         except end;
      Result := true;
  end;
  
  function TMemoEditor.SaveAs(FileName : String) : Boolean;
  begin
      Result := false;
      try
          m_Editor.SaveToFile(FileName);
      except end;
      Result := true;
      end;
  
  很好,這樣的實現已經可以使個部分運作正常了。如果,今後找到更好的編輯器控件,只需要從TEditor派生,再實現一個TXXXEditor類即可,其他部分的代碼不用作任何改動。而且,具體實現的TXXXEditor類中的代碼,只和具體控件本身特性相關(如:讀取、保存文件的方法),而公共邏輯也已經在TEditor類中實現了。
  
  virtual的使用方法,基於筆者個人認識與經驗:
  
  1、如果基類不知道如何實現某方法(只有派生類知道),而基類的其他方法又必須使用該方法,則把該方法聲明為抽象虛方法—— virtual; abstract;(即C++的純虛函數)。
  
  2、如果基類能夠為某方法提供一種默認實現,但派生類可能完全重寫這個實現,則將該方法聲明為虛方法—— virtual;並實現默認算法。

  3、如果基類能夠且必須提供某方法的部分的實現,而派生類必須提供另一部份的實現,則將該方法聲明為非虛方法,並在基類中為其配套提供一個虛方法或抽象虛方法,以允許由基類本身調用和被派生類覆蓋。猶如上例中的Load與DoLoad。
  
  善用virtual,善用多態,你的代碼將更具靈活性!

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