所謂單例就是系統中只能存在某個類的一個執行個體,在現實中只能存在一個執行個體的對象是很常見的,比如系統設定物件只能有一個,
比如一個用戶端同伺服器的TCP/IP串連經常只允許有一個串連等等。下面是一個單例模式的UML圖:
單例模式的實現
那麼如何保證在系統中每時每刻只有一個類的執行個體存在呢,這可以通過靜態變數來實現,在調用GetInstance時判斷靜態變數是否為nil,
如果為nil表示系統中沒有類的執行個體,則構造對象,同時將類執行個體賦值給靜態變數,如果不為nil,則直接返回靜態變數對應的類的執行個體。
Java或者C++中,類中的變數可以修飾為Static表示該變數不依賴於類的執行個體而單獨存在,在Delphi中沒有類似的關鍵字,所以只能是
定義一個單元的私人變數來實現對類執行個體的引用計數。
同時,另外在Java中為了防止使用者使用類的建構函式來建立多個類的執行個體,需要將建構函式的存取屬性改為private,但是在Delphi中,
編譯器對建構函式的保護層級進行了特殊的處理,即便將Contructor方法設定為private存取許可權,編譯器仍然會將Contructor的保護級
別修正為public,因此將調整建構函式的保護層級防止多例的產生在Delphi中是行不通的。幸好TObject基類中定義了NewInstance方法,
這個方式是一個類方法,通過編譯器魔法系統在每次構造對象時都會調用這個類方法,那麼通過重載這個靜態方法,就可以實現對建構函式的控制了。
下面就是一個單例的配置類示意代碼:
TSingleConfig=class(TObject)
private
FConfigPath:
string;
procedure
SetConfigPath(const Value: string);
public
class function
GetInstance():TSingleConfig;
//系統配置路徑
property
ConfigPath:string read FConfigPath write SetConfigPath;
//...省略
class function
NewInstance: TObject; override;
procedure
FreeInstance;override;
end;
implementation
var
GlobalConfig:TSingleConfig=nil;//單元內私人的靜態設定物件變數
{ TSingleConfig
}
procedure
TSingleConfig.FreeInstance;
begin
inherited;
GlobalConfig:=nil;
end;
class function
TSingleConfig.GetInstance: TSingleConfig;
begin
if not
Assigned(GlobalConfig) then
GlobalConfig:=TsingleConfig.Create();
Result:=GlobalConfig;
end;
class function
TSingleConfig.NewInstance: TObject;
begin
if not
Assigned(GlobalConfig) then
GlobalConfig:=TSingleConfig(inherited NewInstance);
Result:=GlobalConfig;
end;
procedure
TSingleConfig.SetConfigPath(const Value: string);
begin
FConfigPath :=
Value;
end;
在靜態類方法GetInstance中,通過對靜態變數GlobalConfig對應的對象進行判斷來首先判斷GlobalConfig變數是否為nil
,如果為nil,則表明系統中還沒有初始化對象的執行個體,這時調用私人的建構函式來初始化對象,並將其賦值給GlobalConfig
變數,如果不為nil,則返回已經建立的GlobalConfig對象。通過GlobalConfig的靜態變數,就可以保證對象的執行個體始終只有
一個(注意的是:GlobalConfig對象需要聲明在Implementation部分,而不要聲明在單元Interface部分,這樣變數對單元外
的使用者是不可見的,這樣可以保護變數不會被使用者誤修改)。
此外,使用者可能會在使用完對象後,將其釋放,在Delphi中,一個對象被釋放後,它的執行個體對應的變數並不會自動設定為nil
,如果之後使用者再次調用GetInstance獲得全域對象時,雖然對象已經被銷毀了,但是Assigned(GlobalConfig)仍然返回為真,
那麼GetInstance就返回一個錯誤的指標,導致AV錯誤。為了避免這種情況,可以重載FreeInstance方法,該方法在對象被釋
放時總是會被調用的,在FreeInstance方法中釋放對象後將GlobalConfig重新設定為nil就可以了。
VCL中的單例
在VCL中也有很多的單例,比如剪貼簿類TClipboard類,在Clipbrd.pas單元中提供了類似於上面的執行個體控制技術,不過它是
通過函數Clipboard來返回剪貼簿的唯一執行個體的,
function Clipboard:
TClipboard;
begin
if FClipboard = nil then
FClipboard := TClipboard.Create;
Result := FClipboard;
end;
同樣的,它也在類的解構函式中將靜態變數設定為nil
destructor TClipboard.Destroy;
begin
if (FClipboard = Self) then
FClipboard := nil;
inherited Destroy;
end;
不過同前面的重載NewInstance的方法相比,VCL中方法缺陷就是不能防止使用者多次建立使用者的執行個體,下面代碼分別
調用TClipboard類和TSingleConfig類的Create方法兩次,然後比較兩次調用後獲得的執行個體的記憶體位址,判斷類是
被建立了幾次:
procedure TForm1.btn1Click(Sender: TObject);
var
P1:Pointer;
P2:Pointer;
begin
P1:=TClipboard.Create();
P2:=Clipboard;
ShowMessage(IntToStr(Integer(p1)));
ShowMessage(IntToStr(Integer(p2)));
end;
procedure TForm1.btn2Click(Sender: TObject);
var
P1:Pointer;
P2:Pointer;
begin
P1:=TSingleConfig.Create();
P2:=TSingleConfig.Create();
ShowMessage(IntToStr(Integer(p1)));
ShowMessage(IntToStr(Integer(p2)));
end;
從運行結果可以知道TClipboard類的建構函式兩次調用後返回的地址不同,而TSingleConfig返回的地址則相同。
可以看出重載NewInstance的方法更加嚴謹,不及出錯。
轉自:http://delphi.sharpplus.com/Delphi/singleton.htm