[DELPHI]單例模式(singleton) 陳省

weixin_30924079發表於2020-04-04

所謂單例就是系統中只能存在某個類的一個例項,在現實中只能存在一個例項的物件是很常見的,比如系統配置物件只能有一個,
比如一個客戶端同伺服器的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的方法更加嚴謹,不及出錯。

轉載於:https://www.cnblogs.com/moon25/archive/2009/11/11/1600676.html

相關文章