Domanda Creazione di un singleton in Delphi utilizzando le nuove funzionalità di D2009 e D2010


Sto cercando di creare un singleton in Delfi. L'ho fatto prima di utilizzare le versioni precedenti di Delphi e ho finito con l'utilizzo di variabili globali (nella sezione dell'implementazione) e l'utilizzo dell'inizializzazione e della finalizzazione per occuparmi dell'istanza. Inoltre non c'era modo di impedire all'utente di creare un'istanza in quanto non si poteva nascondere il costruttore standard. Mi chiedevo se una qualsiasi delle nuove funzionalità come i costruttori di classi e i distruttori e le variabili di classe (ok, non così nuove), forse i generici, potrebbero aiutare a creare una classe generica di singleton. Non sono ancora riuscito a creare qualcosa con mia soddisfazione.


10
2017-09-11 08:01


origine


risposte:


È stato possibile gestirlo sovrascrivendo i metodi di allocazione e deallocator VERO di Delphi, NewInstance e FreeInstance. Costruttori e distruttori in Delphi solo inizializzano e finalizzano rispettivamente, non allocano o deallocano la memoria, quindi tentare di nascondere i costruttori è sempre stato un po 'fuorviante.

cioè, era possibile consentire l'uso gratuito di tutti i costruttori, a condizione di eseguire l'override NewInstance tale che ha sempre restituito un riferimento a una singola allocazione di memoria per la classe.

Ma il tentativo di imporre un modello di utilizzo / comportamento in una classe base è un errore imho. Non tutti i modelli sono o richiedono classi specifiche per incapsulare il modello.

In casi come questo finisci per creare qualcosa che è inutilmente complicato, e la complicazione attira errori nella mia esperienza e l'oggetto dell'esercizio quindi cerca di trovare difetti nell'implementazione del modello e poi cerca di implementare misure di salvaguardia contro tali difetti, piuttosto che andare avanti con il lavoro pratico di lavoro che la classe di singleton avrebbe dovuto svolgere.

È lontano, molto più semplice e più efficace documentare l'uso della classe.

La documentazione come tecnica per implementare questo modello ha funzionato in modo impeccabile per 15 anni Applicazione e Schermo oggetti nel VCL, per esempio, per non parlare di innumerevoli altri singleton che ho creato in quegli anni.


3
2017-09-12 03:22



Se hai solo bisogno di un semplice singleton, il modo più semplice è usare i costruttori di classi e i metodi di classe come suggerito da plainth. Ma i generici sono molto utili se hai bisogno di singleton con costruzione su richiesta (cioè al primo accesso).

Il seguente codice è preso da una delle mie unità di servizio; in pratica fornisce una generica fabbrica singleton per Delphi dal 2009 in poi.

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);

Utilizzo come segue:

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;

12
2017-09-11 08:23



In Delphi 2010 di gran lunga, il modo migliore e più sicuro è quello di utilizzare costruttori di classe. Vedere Qui - leggi in particolare il paragrafo chiamato Incapsulamento migliorato.

HTH.


8
2017-09-11 08:13



Preferisco usare le interfacce quando ho bisogno di singleton e nascondere l'implementazione dell'interfaccia nella sezione di implementazione.

benefici 

  • Distruzione automatica al termine del programma.
  • Nessun modo per creare accidentalmente un TMySingleton.

inconvenienti

  • Qualcuno potrebbe decidere di implementare IMySingleton da solo.

Nota: credo che l'uso di Singletons dovrebbe essere ridotto al minimo. Tutto sommato, i singleton sono poco più di variabili globali glorificate. Se e quando avvii l'unità testando il tuo codice, diventano un fastidio.

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.

7
2017-09-11 08:23



C'è un modo per nascondere il costruttore "Crea" ereditato di TObject. Sebbene non sia possibile modificare il livello di accesso, può essere nascosto con un altro metodo senza parametri pubblico con lo stesso nome: "Crea". Questo semplifica enormemente l'implementazione della classe Singleton. Vedi la semplicità del codice:

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.

Ho aggiunto i dettagli al mio post originale: http://www.yanniel.info/2010/10/singleton-pattern-delphi.html


4
2017-09-11 08:07



Per un singleton, è possibile sovrascrivere il metodo NewInstance. E usa una variabile di classe. La variabile viene creata alla prima chiamata e restituisce il puntatore alla classe ogni altra chiamata.

Hai solo bisogno di trovare qualcosa per distruggerlo alla fine (probabilmente usando il finalize).


3
2017-12-01 22:45



Preferisco creare classi singleton usando un generatore di codice. Il problema con generico è che, tutto il codice viene generato in memoria, non nel file sorgente. Aumenterà la difficoltà del debugging.


0