Создание синглтона в Delphi с использованием новых функций D2009 и D2010 - PullRequest
10 голосов
/ 11 сентября 2009

Я хочу создать синглтон в Delphi. Я сделал это прежде, чем использовать более старые версии Delphi, и в итоге использовал глобальные переменные (в разделе реализации) и использовал инициализацию и финализацию для заботы об экземпляре. Также не было никакого способа предотвратить создание экземпляра пользователем, поскольку вы не могли скрыть стандартный конструктор. Мне было интересно, могут ли какие-либо новые функции, такие как конструкторы классов и деструкторы, и переменные класса (хорошо, не так уж и новые), возможно, обобщенные, помочь в создании универсального одноэлементного класса. Мне пока что не удалось создать что-то для моего удовольствия.

Ответы [ 7 ]

12 голосов
/ 11 сентября 2009

Если вам нужен простой синглтон, самый простой способ - использовать конструкторы классов и методы классов, как это предлагается в методе plainth. Но дженерики очень полезны, если вам нужны синглтоны с конструкцией по запросу (т.е. при первом доступе).

Следующий код взят из одного из моих служебных модулей; в основном он обеспечивает универсальную фабрику синглтонов для 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;
8 голосов
/ 11 сентября 2009

В Delphi 2010, безусловно, самый лучший и безопасный способ - использовать конструкторы класса . См. здесь - особенно внимательно прочитайте параграф Улучшенная инкапсуляция .

НТН.

7 голосов
/ 11 сентября 2009

Я предпочитаю использовать интерфейсы, когда мне нужны синглтоны, и скрывать реализацию интерфейса в разделе реализации.

пособия

  • Автоматическое уничтожение при завершении программы.
  • Нет способа случайно создать TMySingleton.

Недостатки

  • Кто-то может решить внедрить IMySingleton самостоятельно.

Примечание: я считаю, что использование Singletons должно быть сведено к абсолютному минимуму. В общем, синглтоны - это не более, чем прославленные глобальные переменные. Если и когда вы начнете модульное тестирование своего кода, они станут неприятностью.

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.
4 голосов
/ 27 июля 2011

Есть способ скрыть унаследованный конструктор «Create» TObject. Хотя изменить уровень доступа невозможно, его можно скрыть с помощью другого открытого метода без параметров с тем же именем: «Создать». Это значительно упрощает реализацию класса 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

3 голосов
/ 12 сентября 2009

С этим можно было справиться, переопределив методы TRUE allocator и deallocator в Delphi, NewInstance и FreeInstance . Конструкторы и деструкторы в Delphi только инициализируют и финализируют соответственно, они не выделяют и не освобождают память, поэтому попытки скрыть конструкторы всегда были немного ошибочными.

т.е. можно было разрешить свободное использование любого и всех конструкторов, если вы переопределили NewInstance , так что он когда-либо возвращал ссылку только на одно выделение памяти для класса.

Но попытка навязать модель использования / поведения в базовом классе - ошибка imho. НЕ ВСЕ шаблоны являются или требуют определенных классов для инкапсуляции шаблона.

В таких случаях вы в конечном итоге создаете что-то, что становится излишне сложным, а усложнение влечет за собой ошибки в моем опыте, и тогда целью упражнения становится попытка найти недостатки в реализации шаблона, а затем попытаться реализовать меры защиты от этих недостатков. вместо того, чтобы продолжать практическую работу, которую должен был выполнять синглтон-класс.

Гораздо проще и эффективнее документировать ИСПОЛЬЗОВАНИЕ класса.

Документация как метод реализации этого шаблона работала безупречно в течение 15 лет для объектов Application и Screen в VCL, например, не говоря уже о бесчисленных других синглетонах, которые я создал в те годы.

3 голосов
/ 11 сентября 2009

Для одиночного файла вы можете переопределить метод NewInstance. И используйте переменную класса. Вы создаете переменную при первом вызове и возвращаете указатель на класс при каждом вызове.

Вам просто нужно найти что-нибудь, чтобы уничтожить его в конце (возможно, с помощью финализатора).

0 голосов
/ 02 декабря 2009

Я предпочитаю создавать синглтон-класс, используя генератор кода. Проблема с универсальным заключается в том, что весь код генерируется в памяти, а не в исходном файле. Это увеличит сложность отладки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...