web-dev-qa-db-fra.com

Comment faire pour abstraire une classe singleton?

C'est comme ça que j'écris mes cours de singleton.

public class MyClass
{
    /// <summary>
    /// Singleton
    /// </summary>
    private static MyClass instance;

    /// <summary>
    /// Singleton access.
    /// </summary>
    public static MyClass Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new MyClass();
            }
            return _instance;
        }
    }

    private MyClass() { .... }
}

Comment créer un motif singleton réutilisable?

Les modèles Singleton présentent les défis suivants.

  • Le constructeur est private ou protected.
  • Une classe de base ne peut pas instancier une classe héritée. Vous pouvez donc réutiliser un résumé MyAbstractSingletonClass commun.
  • Il doit avoir une propriété locale en lecture seule pour obtenir l'instance.

Le problème

J'utilise ce modèle sur plusieurs classes et je dois toujours écrire le même code. Comment puis-je écrire quelque chose qui est réutilisé chaque fois que j'ai besoin d'un singleton?

28
cgTag

Vous pouvez y parvenir en combinant une contrainte de type générique - auto-référençant et une contrainte de type " new () ".

La "nouvelle" contrainte garantit que toute classe enfant aura toujours un constructeur sans paramètre, donc _instance = new T(); fonctionnera toujours.

La contrainte de type à auto-référencement garantit que la propriété statique "Instance" renvoie toujours le type correct; pas le type "base". Votre classe de base singleton ressemblerait à ceci:

public abstract class SingletonBase<T> 
    where T : SingletonBase<T>, new()
{
    private static T _instance = new T();
    public static T Instance
    {
        get
        {                
            return _instance;
        }   
    }
}

Vos cours pour enfants ressembleront à ceci:

public class MyChildSingleton : SingletonBase<MyChildSingleton>
{
    //Done!
}

Bien sûr, si vous voulez que votre singleton soit d'usage général, vous devez également modifier légèrement votre code "create singleton instance", pour utiliser le modèle " double-check lock " ou le Lazy class , pour le rendre thread-safe.

La grande mise en garde: si vous utilisez cette méthode, la contrainte "new ()" garantit à peu près que votre classe aura toujours un constructeur public, sans paramètre. Cela signifie que vos utilisateurs finaux peuvent toujours appeler new MyChildSingleton() s'ils le souhaitent vraiment, en contournant entièrement votre instance singleton. Votre singleton serait "par convention", au lieu d'être strictement appliqué. Pour résoudre ce problème, il faudrait un peu plus d'ingénierie. Dans le scénario ci-dessus, la convention semble être que vous devez nommer votre instance statique "Default" au lieu de "Instance." Ceci traduit subtilement le fait que votre classe offre une instance de singleton 'suggérée', mais son utilisation est techniquement optionnelle.

J'ai fait quelques tentatives pour appliquer strictement le modèle singleton, et le résultat final a été d'utiliser la réflexion pour appeler manuellement un constructeur privé. Vous pouvez voir ma tentative de code complet ici .

48
BTownTKD

La vraie solution commence avec l'approche de BTownTKD, mais en l'augmentant avec la méthode Activator.CreateInstance qui permet à vos classes enfants de conserver les constructeurs privés.

Classe parentale

public abstract class BaseSingleton<T> where T :
    BaseSingleton<T>
{
    private static readonly ThreadLocal<T> Lazy =
        new ThreadLocal<T>(() =>
            Activator.CreateInstance(typeof(T), true) as T);

    public static T Instance => Lazy.Value;
}

Classe enfant

public sealed class MyChildSingleton : BaseSingleton<MyChildSingleton>
{
    private MyChildSingleton() { }
}

Exemple d'implémentation complète ici

5
Buvy

En ajoutant à la réponse de BTownTKD, il est en fait assez simple de restreindre un appel de constructeur au moment de l'exécution (ce n'est pas possible en compilation). Tout ce que vous faites est d'ajouter un constructeur protégé dans SingletonBase, qui lève une exception si _instance n'est pas null. L'exception sera levée même si le constructeur est la première chose à appeler de l'extérieur.

J'ai réussi à appliquer cette technique dans une base individuelle, et à la rendre paresseuse et sécurisée comme décrit ci-dessous: http://csharpindepth.com/Articles/General/Singleton.aspx

Le résultat (avec explication d'utilisation):

/// <summary>
/// Generic singleton class, providing the Instance property, and preventing manual construction.
/// Designed as a base for inheritance trees of lazy, thread-safe, singleton classes.
/// Usage:
/// 1. Sub-class must use itself, or its sub-class, as the type parameter S.
/// 2. Sub-class must have a public default constructor (or no constructors).
/// 3. Sub-class might be abstract, which requires it to be generic and demand the generic type
///    have a default constructor. Its sub-classes must answer all these requirements as well.
/// 4. The instance is accessed by the Instance getter. Using a constructor causes an exception.
/// 5. Accessing the Instance property in an inner initialization in a sub-class constructor
///    might cause an exception is some environments.
/// </summary>
/// <typeparam name="S">Lowest sub-class type.</typeparam>
public abstract class Singleton<S> where S : Singleton<S>, new()
{
    private static bool IsInstanceCreated = false;
    private static readonly Lazy<S> LazyInstance = new Lazy<S>(() =>
        {
            S instance = new S();
            IsInstanceCreated = true;
            return instance;
        });

    protected Singleton()
    {
        if (IsInstanceCreated)
        {
            throw new InvalidOperationException("Constructing a " + typeof(S).Name +
                " manually is not allowed, use the Instance property.");
        }
    }

    public static S Instance
    {
        get
        {
            return LazyInstance.Value;
        }
    }
}

Je dois dire que je n'ai pas fait de test multi-threading intensif, mais comme certains l'ont déjà dit, vous pouvez toujours utiliser le vieux truc du double contrôle.

5
Eugene Marin

Vous avez raison - dans l'état actuel des choses, vous ne pouvez pas atteindre cet objectif. Mais vous pouvez l'approcher en utilisant des génériques, notez qu'avec cette approche, vous obtiendrez une instance singleton pour chaque type dérivé unique:

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            var x = new MyDerivedClass();
            Console.WriteLine(x.ToString());
            Console.WriteLine(x.Instance.ToString());

            Console.ReadKey();
        }
    }


    public abstract class MyBaseClass<T> where T : class, new()
    {
        protected T GetInstance()
        {
            if (_instance == null)
            {
                lock (_lockObj)
                {
                    if (_instance == null)
                        _instance = new T();
                }
            }
            return _instance;
        }

        public T Instance
        {
            get { return GetInstance(); }
        }

        private volatile static T _instance;
        private object _lockObj = new object();
    }

    public class MyDerivedClass : MyBaseClass<MyDerivedClass>
    {
        public MyDerivedClass() { }
    }

}
3
slugster

La réponse simple est que vous ne pouvez pas implémenter le modèle singleton dans une classe de base.

Cependant, vous pouvez implémenter d'autres modèles de conception de création pouvant convenir à ce que vous essayez d'accomplir. Par exemple, jetez un oeil à Abstract Factory .

2
Ilya Kogan

Une implémentation générique et rationalisée du modèle thread-safe lockless :

public abstract class Singleton<T> where T : class, new() {
    public static readonly T Instance = new T();
}

Le constructeur statique est exclu au profit de la performance, car la paresse n'est pas requise et la propriété est remplacée par un champ public par souci de brièveté.

La mise en oeuvre

public sealed class Foo : Singleton<Foo> {
    public void Bar() {
        //...
    }
}

Utilisation

Foo.Instance.Bar();
1
2Toad

J'ai récemment suggéré cette réponse à une question connexe:

https://stackoverflow.com/a/20599467

Avec cette méthode, la classe de base gère la création de toutes les instances de classes dérivées, car tous les constructeurs de classes dérivées nécessitent un objet que seule la classe de base peut fournir, et la restriction de constructeur sans paramètre est inutile.

0
dan

Je crois qu'avec la solution de @ Buvy, vous obtiendrez une instance par thread qui pourrait correspondre à ce qu'il voulait. Vous devrez le modifier un peu pour obtenir une seule instance sur plusieurs threads.

public abstract class BaseSingleton<T> 
    where T : BaseSingleton<T>
{
    private static readonly Lazy<T> lazy =
                    new Lazy<T>(() => Activator.CreateInstance(typeof(T), true) as T);

    public static T Instance { get { return lazy.Value; } }

}
0
jalley

Mon exemple suggéré:

classe de base

public abstract class SingletonBase<T> where T : class
{
  private static readonly Lazy<T> sInstance = new Lazy<T>(() => CreateInstanceOfT());

  public static T Instance { get { return sInstance.Value; } }

  private static T CreateInstanceOfT()
  {
    return Activator.CreateInstance(typeof(T), true) as T;
  }
}

_ {Usage} _

public class yourClass : SingletonBase<yourClass>
{
   public yourMethod()
   {
   }
}

utilisez votre classe singleton comme ceci:

yourClass.Instance.yourMethod();

pour plus d'informations voir ma source de réponse dans ce lien

0
Hasan Fathi