所谓单例就是系统中只能存在某个类的一个实例,在现实中只能存在一个实例的对象是很常见的,比如系统配置对象只能有一个,
比如一个客户端同服务器的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
的方法更加严谨,不及出错。