2009-09-11 15 views
10

Sto cercando di creare un singleton in Delphi. 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.Creazione di un singleton in Delphi utilizzando le nuove funzionalità di D2009 e D2010

risposta

3

E 'stato possibile gestire questo sovrascrivendo l'allocatore e deallocatore metodi vero in 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è è stato possibile consentire l'uso gratuito di qualsiasi e tutti i costruttori il tempo che escludeva NewInstance tale che sempre e solo restituito un riferimento ad una sola dotazione 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 si finisce 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 quelli difetti, piuttosto che andare avanti con il lavoro pratico di lavoro che la classe singleton avrebbe dovuto svolgere.

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

Documentazione come tecnica per l'attuazione di questo schema ha funzionato senza problemi per 15 anni per la Application e schermo oggetti nella VCL, ad esempio, per non parlare di innumerevoli altri single che ho creato in quegli anni.

3

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 la finalizzazione).

+0

o un distruttore di classe in D2010. –

8

In Delphi 2010 il modo migliore e più sicuro è utilizzare i costruttori di classe . Vedi here - leggi in particolare il paragrafo chiamato Incapsulamento migliorato.

HTH.

+1

È un peccato che non si siano preoccupati di documentare correttamente questa funzione, o di includerla nelle "Novità" nella guida. – IanH

7

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

beneficia

  • distruzione automatica quando il programma termina.
  • Impossibile creare accidentalmente un TMySingleton.

svantaggi

  • Qualcuno potrebbe decidere di attuare 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. 
+0

+1 per la parte in corsivo su singleton. – mghie

+0

@mghie: non sei d'accordo sull'implementazione del pattern? –

+0

Sono d'accordo con te sul fatto che la maggior parte degli usi del modello Singleton non sono progressi rispetto alle variabili globali. Ma se uno vuole averli (per qualsiasi ragione), allora sarebbe necessaria una soluzione generica thread-safe. Il tuo non lo è, quindi potresti finire con più di un'istanza. Almeno non perde memoria, qualcosa che ho visto nelle grandi librerie commerciali dove gli oggetti vengono usati per l'implementazione di singleton invece delle interfacce. La soluzione di Moritz sembra essere thread-safe, ma dovrebbe essere controllata a fondo. – mghie

12

Se avete solo bisogno di un Singleton pianura, il modo più semplice è quello di utilizzare costruttori della classe e metodi di classe, come suggerito dal 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); 

Uso 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; 
+2

Grande codice Moritz. –

+0

Grazie. In effetti sarebbe più bello se potessi rimuovere i workarounds per D2009 :) –

0

Preferisco creare una classe singleton utilizzando 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.

4

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. Vedere 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

Problemi correlati