使用D2009和D2010的新功能在Delphi中创建单例

时间:2009-09-11 08:01:34

标签: delphi design-patterns singleton

我想在Delphi中创建一个单例。我在使用旧版本的Delphi之前完成了这个,最后使用全局变量(在实现部分中)并使用初始化和终结来处理实例。此外,无法阻止用户创建实例,因为您无法隐藏标准构造函数。我想知道是否有任何新的功能,如类构造函数和析构函数,以及类变量(好的,不是那么新),也许是泛型,可以帮助创建一个通用的单例类。我还没有设法让我感到满意。

7 个答案:

答案 0 :(得分:12)

如果你只需要一个普通的单例,最简单的方法是使用plainstan建议的类构造函数和类方法。但是,如果您需要按需构建的单身人士(即首次访问时),仿制药非常有用。

以下代码来自我的一个实用单位;它基本上为Delphi 2009以后提供了一个通用的单件工厂。

interface

type
  {$HINTS OFF}
  { TSingletonInstance<> implements lazy creation, which is sometimes useful for avoiding
    expensive initialization operations.
    If you do not require lazy creation and you target only Delphi 2010 onwards, you should
    use class constructors and class destructors instead to implement singletons. }
  TSingletonInstance<T: class, constructor> = record
  private
    FGuard: IInterface;
    FInstance: T;
    function GetInstance: T;
    function CreateInstance: TObject;
  public
    property Instance: T read GetInstance;
  end;
  {$HINTS ON}
  TSingletonFactoryFunction = function: TObject of object;

{ Private symbols (which are in the interface section because of known limitations of generics) }
procedure _AllocateSingletonInstance (InstanceRecord: Pointer; Factory: TSingletonFactoryFunction);

implementation

{ TSingleton }

var
  SingletonCriticalSection: TRTLCriticalSection;

type
  TSingletonGuard = class (TInterfacedObject)
  private
    FSingletonInstance: TObject;
  public
    constructor Create (AInstance: TObject);
    destructor Destroy; override;
  end;

  PUntypedSingletonInstance = ^TUntypedSingletonInstance;
  TUntypedSingletonInstance = record
    FGuard: IInterface;
    FInstance: TObject;
  end;

  // TODO: is a lock required for multiple threads accessing a single interface variable?
procedure _AllocateSingletonInstance (InstanceRecord: Pointer; Factory: TSingletonFactoryFunction);
var
  USI: PUntypedSingletonInstance;
begin
  USI := PUntypedSingletonInstance (InstanceRecord);
  EnterCriticalSection (SingletonCriticalSection);
  if USI.FInstance = nil then
  begin
    USI.FInstance := Factory ();
    USI.FGuard := TSingletonGuard.Create (USI.FInstance);
  end;
  LeaveCriticalSection (SingletonCriticalSection);
end;

constructor TSingletonGuard.Create (AInstance: TObject);
begin
  FSingletonInstance := AInstance;
end;

destructor TSingletonGuard.Destroy;
begin
  FSingletonInstance.Free;
  inherited;
end;

function TSingletonInstance<T>.GetInstance: T;
var
  Factory: TSingletonFactoryFunction;
begin
  if FInstance = nil then
  begin
    Factory := Self.CreateInstance; // TODO: associate QC report
    _AllocateSingletonInstance (@Self, Factory);
  end;
  Result := FInstance;
end;

function TSingletonInstance<T>.CreateInstance: TObject;
begin
  Result := T.Create;
end;

initialization
  InitializeCriticalSection (SingletonCriticalSection);
finalization
  DeleteCriticalSection (SingletonCriticalSection);

用法如下:

type
  TMySingleton = class
  public
    constructor Create;
    class function Get: TMySingleton; static;
  end;

var
  MySingletonInstance: TSingletonInstance<TMySingleton>;

class function TMySingleton.Get: TMySingleton;
begin
  Result := MySingletonInstance.Instance;
end;

答案 1 :(得分:8)

到目前为止,在Delphi 2010中,最好和最安全的方法是使用类构造函数。请参阅here - 特别阅读名为改进的封装的段落。

HTH。

答案 2 :(得分:7)

我更喜欢在需要单例时使用接口,并在实现部分隐藏接口的实现。

好处

  • 程序终止时自动销毁。
  • 无法意外创建TMySingleton。

<强>缺点

  • 有人可能决定自己实施IMySingleton。

注意:我认为单身人士的使用应该保持在最低限度。总而言之,单身人士只不过是美化的全球变数。如果您开始对代码进行单元测试,那么它们就会变得很麻烦。

unit uSingleton;

interface

type
  ISingleton = interface
    ['{8A449E4B-DEF9-400E-9C21-93DFA2D5F662}']
  end;

function Singleton: ISingleton;

implementation

uses
  SyncObjs;

type
  TSingleton = class(TInterfacedObject, ISingleton);

var
  Lock: TCriticalSection;

function Singleton: ISingleton;
const
  _singleton: ISingleton = nil;
begin
  if not Assigned(_singleton) then
  begin
    Lock.Acquire;
    try
      if not Assigned(_singleton) then
        _singleton := TSingleton.Create();
    finally
      Lock.Release;
    end;
  end;
  Result := _singleton;
end;

initialization
  Lock := TCriticalSection.Create;
finalization
  Lock.Free;

end.

答案 3 :(得分:4)

有一种方法可以隐藏TObject的继承“Create”构造函数。虽然无法更改访问级别,但可以使用另一个具有相同名称的公共无参数方法隐藏它:“创建”。这极大地简化了Singleton类的实现。请参阅代码的简单性:

unit Singleton;

interface

type
  TSingleton = class
  private
     class var _instance: TSingleton;
  public
    //Global point of access to the unique instance
    class function Create: TSingleton;

    destructor Destroy; override;
  end;

implementation

{ TSingleton }

class function TSingleton.Create: TSingleton;
begin
  if (_instance = nil) then
    _instance:= inherited Create as Self;

  result:= _instance;
end;

destructor TSingleton.Destroy;
begin
  _instance:= nil;
  inherited;
end;

end.

我在原帖中添加了详细信息:http://www.yanniel.info/2010/10/singleton-pattern-delphi.html

答案 4 :(得分:3)

对于单例,您可以覆盖NewInstance方法。并使用类变量。您在第一次调用时创建变量,并将指针返回给彼此调用的类。

你最后需要找到一些东西来销毁它(可能使用finalize)。

答案 5 :(得分:3)

可以通过覆盖Delphi中的TRUE分配器和解除分配器方法, NewInstance FreeInstance 来管理它。 Delphi中的构造函数和析构函数只分别初始化和终结,它们不分配或释放内存,因此试图隐藏构造函数总是有点误导。

即。只要你覆盖 NewInstance ,就可以允许任意和所有构造函数的自由使用,这样它只会返回对类的一个单独内存分配的引用。

但是尝试在基类中强制执行使用/行为模式是一个错误。并非所有模式都是或需要特定的类来封装模式。

在这样的情况下,你最终会创建一些不必要的复杂的东西,并且复杂性会吸引我的经验中的错误,然后练习的对象会试图找到模式实现中的缺陷,然后尝试实施针对这些缺陷的保护措施而不是继续单身类应该执行的实际工作。

记录班级的用法是远远的,更简单,更有效。

作为实现此模式的技术的文档在VCL中的应用程序屏幕对象已经完美运行了15年,例如,更不用说无数其他单例我在那些年里创造的。

答案 6 :(得分:0)

我更喜欢使用代码生成器创建单例类。泛型的问题在于,所有代码都是在内存中生成的,而不是在源文件中生成的。这会增加调试的难度。