兩種Delphi實作Singleton模式方法
haozi
摘要本文描述了兩種Singleton模式的Delphi實作方式,並做了比較分析。
關鍵字設計模式,Singleton
Singleton(單件)模式是一種很有用的設計模式。它的意圖的是:僅僅創建一個類別的實例,並提供一個存取它的全局存取點。全域變數使的一個物件易被訪問,但不能防止你實例化多個物件。單件模式的目的是確保在程式的生命週期內只有一個實例存在。
看下面的程式碼:
PRocedure TForm1. Button1Click(Sender: TObject);
var lS1 : TSingleton; l
S2 : TSingleton;
begin
try lS1 := TSingleton.Create; ////呼叫類別的建構器
lS2 := TSingleton.Create; ////呼叫類別的建構器
//// ...別的程式碼
finally
lS1.Free; ////釋放對象
lS2.Free; ////釋放對象
end;
end;
在上面的程式碼中第一次呼叫Create函數時TSingleton類別被實例化,lS1指向一個記憶體存放物件的位址,當第二次呼叫TSingleton.Create函數時又重新實例化了TSingleton物件,lS2指向記憶體分配的另一個地址。 Singleton模式就是讓類別自己負責保存他的唯一實例。
在上面的程式碼中就是讓lS2創建的時候也指向lS1指向的物件(也就是被分配同一個記憶體位址),同樣我們在釋放lS1時必須防止記憶體被釋放,因為單件物件也被lS2所引用。從而保證在程式的生命週期內有且只有一個類別實例。
《設計模式》C++的範例程式碼是使用C++的靜態成員變數保存實例的,同時使用protected的建構子函數。但是在Delphi中由於沒有靜態成員變量,所以不能原樣的使用該單件模式範例的方法。以下我們分析兩種DELPHI實現Singleton模式的幾種方法。
一.基於override兩個Tobject虛擬函數的方法
class function NewInstance: TObject; virtual;
procedure FreeInstance; virtual;
NewInstance函數負責類別物件創建的時候為物件分配內存,FreeInstance則相反地釋放內存
。
前者在物件建構時調用,後者在物件析構時調用。
我們使用兩個全域變數來保存單件物件和物件的參考記數。
var Instance : TSingleton = nil;
RefCount : Integer = 0;
TSingleton類別的單元:
////---------------------------------------------------------- -----------------------------
////
unit uSingleton;
interface
type
TSingleton = class(TObject)
public
class function NewInstance: TObject; override; ////覆蓋基底類別函數
procedure FreeInstance; override; ////覆蓋基底類別函數
class function RefCount: Integer;////傳回目前引用記數
end;
//// Declaration global variables
var
Instance: TSingleton = nil;
RefCount: Integer = 0;
implementation
{ TSingleton }
procedure TSingleton.FreeInstance;
begin
Dec( RefCount );////減少引用記數
if ( RefCount = 0 ) then////是否為0,是則釋放內存
begin
Instance := nil;
//// 釋放單件類別的私有變數
////…
inherited FreeInstance;
end;
end;
class function TSingleton.NewInstance: TObject;
begin
if ( not Assigned( Instance ) ) then
begin
Instance := TSingleton(inherited NewInstance);
////初始化私有變數範例:
//// Instance.VariableName := Value;
end;
Result := Instance ;
Inc( RefCount );
end;
class function TSingleton.RefCount: Integer;
begin
Result := RefCount;
end;
end.
////---------------------------------------------------------- -----------------------------
////
當呼叫TSingleton的建構器的時候,會呼叫我們override的NewInstance函數,由NewInstance分配記憶體並回傳給建構器,這樣透過override的NewInstance函數我們確保了Create函數只可能實例化一個TSingleton物件(無論呼叫多少次Create只傳回第一次分配的記憶體位址)。同時RefCount變數保存我們有幾個到物件的參考。
我們在來看測試程式碼
procedure TForm1.Button1Click(Sender: TObject);
var
lS1, lS2: TSingleton;
Ob1, Ob2: Tobject;
begin
lS1 := TSingleton.Create;
ShowMessage(IntToStr(RefCount)); //// Ref_Count = 1
lS2 := TSingleton.Create;
ShowMessage(IntToStr(RefCount)); //// Ref_Count = 2
Ob1 := TObject.Create;
Ob2 := Tobject.Create;
if lS1 = lS2 then
ShowMessage('位址相等') //// lS1 = lS2
else
ShowMessage('位址不相等');
if Ob1 = Ob2 then
ShowMessage('位址相等')
else
ShowMessage('位址不相等'); //// Ob1 <> Ob2
end;
當程式呼叫析構器的時候(就是呼叫FREE函數的時候),析構器會呼叫FreeInstance函式釋放被建構器所分配的記憶體。 Override的FreeInstance函數保證引用記數為零的時候才釋放單件模式物件的記憶體。
下面是我們的測試程式碼:
var
lS1 : TSingleton;
lS2 : TSingleton;
begin
try
lS1 := TSingleton.Create; ////呼叫類別的建構器
lS2 := TSingleton.Create; ////呼叫類別的建構器
//// ...別的程式碼
finally
lS1.Free; ////這裡會先呼叫我們覆蓋定義的FreeInstance,
////由於這時RefCount在減1後為1,單件物件沒有被釋放
lS2.Free; ////dec(RefCount)= 0 釋放單件對象
end;
end;
上面這種單件模式實作方法很好地實現了由類別本身來負責保存自己的唯一實例(透過截取創建新物件的請求-參考《設計模式》。它對TSingleton類別的使用沒有特殊的限制— —程式設計師可以隨意的呼叫Create和Free函數。
本模式的缺點是:此TSingleton類別不能作為父類別繼承生成子類別。如果繼承產生兩個子類,Create時只產生一個物件。
procedure TForm1.Button1Click(Sender: TObject);
var
lS1 : 子類別一;
lS2: 子類別二;
begin
lS1 := 子類別一.Create;
lS2 := 子類別二.Create; ////不會建立子類別二,lS2將指向lS1指向的內存,
////也就是lS1 = lS2end;
二. 《設計模式》上範例的Delphi實現
《設計模式》的實作範例是透過私有建構器函數來實現控制只產生一個物件實例。但該給出的C++程式碼實作未給出物件如何釋放。 Delphi裡面不能實作Create函數的私有化,我們新定義一個函數來取代Create函數,同時屏蔽父類別的Create函數。程式碼如下
:
////---------------------------------------------------------- -----------------------------
////
unit uSingletonUnit;
interface
uses
Classes, SysUtils;
type
TCSingleton = class(TComponent) ////從Tcomponent類別繼承來。
private
constructor CreateInstance(AOwner: TComponent); ////傳遞Owner參數
//// 這樣TCSingleton類別物件就會跟著Owner銷毀(擁有者負責銷毀TCSingleton物件)
public
constructor Create(AOwner: TComponent); override;
class function Instance(AOwner: TComponent): TCSingleton;
end;
var
gCSingleton: TCSingleton; //// Global variables
implementation
{ TCSingleton }
constructor TCSingleton.Create(AOwner: TComponent);
begin
////屏蔽Create函數的功能
raise Exception.CreateFmt('access class %s through Instance only',
[ClassName]);
end;
constructor TCSingleton.CreateInstance(AOwner: TComponent);
begin
////新定義的建構子Private型的
inherited Create(AOwner);
end;
class function TCSingleton.Instance(AOwner: TComponent): TCSingleton;
begin
if not Assigned(gCSingleton) then
gCSingleton := TCSingleton.CreateInstance(AOwner);
Result := gCSingleton;
end;
end.
////---------------------------------------------------------- ----------------------------/
/
在上面的實作類別使用過程中,程式設計師不用考慮單件模式物件的銷毀問題。只是不能呼叫Create,必須呼叫Instance函數來取得物件的實例,同時把單件擁有者當作參數傳遞到函數裡。這種實作方法可以當作基底類別被繼承,用在狀態模式的單件裡(參考文獻4),實現執行時的多態。
三.結束語
Singleton 模式的Delphi實作在網路上還能查出其他一些實作的方式,本文的兩種方法上
最常見的和簡單的。同時其它方法的思路也很跟以上兩種方法很相似。