• [DELPHI]单例模式(singleton) 陈省


    所谓单例就是系统中只能存在某个类的一个实例,在现实中只能存在一个实例的对象是很常见的,比如系统配置对象只能有一个,
    比如一个客户端同服务器的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的方法更加严谨,不及出错。
  • 相关阅读:
    Objective-C传递数据小技巧
    得到当前活动的controller
    ios7去除手势滑动返回
    生活小常识
    通过email分享
    release下去除nslog宏
    AFNetworking VS ASIHTTPRequest
    web服务器和应用服务器
    mac 搭建git服务器
    UIKit基础:14-序列帧动画的简单介绍
  • 原文地址:https://www.cnblogs.com/moon25/p/1600676.html
Copyright © 2020-2023  润新知