2009-09-11 6 views
10

Je cherche à créer un singleton en Delphi. Je l'ai fait avant d'utiliser des versions plus anciennes de Delphi, et j'ai fini par utiliser des variables globales (dans la section implémentation) et en utilisant l'initialisation et la finalisation pour prendre soin de l'instance. De plus, il n'y avait aucun moyen d'empêcher l'utilisateur de créer une instance car vous ne pouviez pas masquer le constructeur standard. Je me demandais si l'une des nouvelles fonctionnalités telles que les constructeurs de classe et les destructeurs, et les variables de classe (ok, pas si nouveau), peut-être génériques, pourraient aider à créer une classe singleton générique. Je n'ai pas encore réussi à créer quelque chose à ma satisfaction.Création d'un singleton dans Delphi en utilisant les nouvelles fonctionnalités de D2009 et D2010

Répondre

3

Il est possible de gérer en surchargeant les méthodes TRUE allocateur et deallocator à Delphes, NewInstance et FreeInstance.Les constructeurs et les destructeurs de Delphi ne s'initialisent et ne se finalisent respectivement, ils n'allouent pas ou ne libèrent pas la mémoire, alors essayer de cacher les constructeurs était toujours un peu erroné.

à-dire qu'il est possible de permettre l'utilisation gratuite de tout et de tous les constructeurs aussi longtemps que vous NewInstance redéfini tel qu'il ne revenait jamais une référence à une seule allocation de mémoire pour la classe. Mais essayer de faire respecter un modèle d'utilisation/comportement dans une classe de base est une erreur imho. Tous les patterns ne sont pas ou ne nécessitent pas de classes spécifiques pour encapsuler le pattern. Dans de tels cas, vous finissez par créer quelque chose qui est inutilement compliqué, et la complication attire les erreurs dans mon expérience et l'objet de l'exercice devient alors de trouver des failles dans la mise en œuvre du modèle et d'essayer de mettre en place défauts, plutôt que de poursuivre le travail pratique que la classe singleton était censée accomplir.

Il est loin, plus simple et plus efficace de documenter l'utilisation de la classe.

Documentation comme une technique pour la mise en œuvre de ce modèle a fonctionné parfaitement pendant 15 ans pour l'application et écran objets dans la VCL, par exemple, sans parler d'innombrables autres singletons que j'ai créé dans les années.

3

Pour un singleton, vous pouvez remplacer la méthode NewInstance. Et utilisez une variable de classe. Vous créez la variable au premier appel et renvoyez le pointeur vers la classe l'un l'autre appel.

Vous avez juste besoin de trouver quelque chose pour le détruire à la fin (probablement en utilisant la finalisation).

+0

ou une destructor de classe D2010. –

8

Dans Delphi 2010 de loin, la meilleure et la plus sûre consiste à utiliser constructeurs de classe. Voir here - lire en particulier le paragraphe appelé Amélioration de l'encapsulation.

HTH.

+1

Il est dommage qu'ils n'aient pas pris la peine de documenter cette fonctionnalité correctement, ou l'inclure dans le "Quoi de neuf" dans l'aide. – IanH

7

Je préfère utiliser des interfaces lorsque j'ai besoin de singletons et cacher l'implémentation de l'interface dans la section implémentation.

bénéficie

  • destruction automatique lorsque le programme se termine.
  • Impossible de créer accidentellement un TMySingleton.

inconvénients

  • Quelqu'un pourrait décider de mettre en œuvre IMySingleton lui-même.

Note: Je crois que l'utilisation de Singletons devrait être maintenue à un minimum absolu. Dans l'ensemble, les singletons sont un peu plus que des variables globales glorifiées. Si et quand vous commencez à tester votre code, ils deviennent une nuisance.

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 pour la partie italique sur les singletons. – mghie

+0

@mghie: vous n'êtes pas d'accord sur la mise en œuvre du modèle? –

+0

Je suis d'accord avec vous que la plupart des utilisations du modèle Singleton ne sont pas un progrès par rapport aux variables globales. Mais si l'on veut les avoir (pour une raison quelconque), alors une solution générique sans danger pour les threads serait nécessaire. Le vôtre n'est certainement pas, de sorte que vous pouvez finir avec plus d'une instance. Au moins, il ne fuit pas la mémoire, quelque chose que j'ai vu dans les grandes bibliothèques commerciales où les objets sont utilisés pour l'implémentation singleton au lieu des interfaces. La solution de Moritz semble être sûre pour les threads, mais cela devrait être vérifié minutieusement. – mghie

12

Si vous avez juste besoin d'un singleton simple, la façon la plus simple est d'utiliser les constructeurs de classe et les méthodes de classe comme suggéré par plainth. Mais les génériques sont très utiles si vous avez besoin de singletons avec construction à la demande (c'est-à-dire en premier accès).

Le code suivant provient d'une de mes unités utilitaires; il fournit essentiellement une usine générique singleton pour Delphi 2009 et au-delà.

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

Utilisation comme suit:

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

Bon code Moritz. –

+0

Merci. En fait, il serait plus joli si je pouvais supprimer les solutions de contournement pour D2009 :) –

0

Je préfère créer une classe singleton en utilisant un générateur de code. Le problème avec générique est que tout le code est généré en mémoire, pas dans le fichier source. Cela augmentera la difficulté du débogage.

4

Il existe un moyen de masquer le constructeur "Create" hérité de TObject. Bien qu'il ne soit pas possible de changer le niveau d'accès, il peut être caché avec une autre méthode publique sans paramètre avec le même nom: "Créer". Cela simplifie énormément l'implémentation de la classe Singleton. Voir la simplicité du code:

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. 

J'ai ajouté les détails à mon message original: http://www.yanniel.info/2010/10/singleton-pattern-delphi.html

Questions connexes