Pergunta

Como faço para fazer uma classe abstrata que deve forçar cada classes derivadas a ser singleton? Eu uso C#.

Foi útil?

Solução

Quando você deseja fazer a verificação do tempo, isso não é possível. Com a verificação do tempo de execução, você pode fazer isso. Não é bonito, mas é possível. Aqui está um exemplo:

public abstract class Singleton
{
    private static readonly object locker = new object();
    private static HashSet<object> registeredTypes = new HashSet<object>();

    protected Singleton()
    {
        lock (locker)
        {
            if (registeredTypes.Contains(this.GetType()))
            {
                throw new InvalidOperationException(
                    "Only one instance can ever  be registered.");
            }
            registeredTypes.Add(this.GetType());
        }
    }
}

public class Repository : Singleton
{
    public static readonly Repository Instance = new Repository();

    private Repository()
    {
    }
}

Outras dicas

Isso não funcionaria porque o singleton precisa de um lugar estático e isso não pode ser forçado.

Para singletonimplemention + exemplos, consulte: Implementando o padrão Singleton em C#

Singleton significa ter construtores privados. Mas você sabe que os membros particulares não podem ser herdados. Em C ++, havia modelos, para que você possa criar um singleton a partir de uma classe de modelo. Em C#, não há modelos, então você precisa escrever seus próprios construtores privados para cada singleton que deseja.

Aqui está uma maneira (feia) de fazer isso. Provavelmente poderia ser simplificado e melhorado, mas é a minha primeira tentativa.

A idéia é primeiro fazer com que a classe abstrata genérica da Classe Base A (conforme mencionado nos comentários acima), mas o parâmetro de tipo é restrito a derivar da própria classe base. Isso permite que a classe base lide com a instância singleton do tipo derivado. Nota Todas as classes derivadas devem ser seladas, como em qualquer classe Singleton.

Em seguida, é permitido um construtor protegido, mas é obrigado a aceitar uma instância de uma classe especial, Singletonkey, que é um singleton modificado. As classes derivadas têm acesso à definição da classe Singletonkey, mas a classe base mantém o controle privado sobre a única instância permitida e, portanto, a construção de todos os objetos derivados.

Terceiro, a classe base precisará chamar o construtor da classe derivada, mas isso é um pouco complicado. O compilador reclamará se você tentar chamar o construtor com chave derivada, pois não é garantido que ele não existe. A solução é adicionar um delegado estático que a classe derivada deve inicializar. Portanto, quaisquer classes derivadas precisarão fornecer um método simples de inicialização. Esse método de inicialização deve ser chamado explicitamente antes de tentar acessar a instância pela primeira vez no código, ou resultará em um erro de tempo de execução.

public abstract class Singleton<T> where T : Singleton<T>
{
    protected Singleton(SingletonKey key) { }
    private static SingletonKey _key;
    private static SingletonKey Key
    {
        get
        {
            if (_key == null) SingletonKey.Initialize();
            return _key;
        }
    }
    protected class SingletonKey
    {
        private SingletonKey()
        {
        }
        public static void Initialize()
        {
            if (_key == null)
            {
                _key = new SingletonKey();
            }
        }
    }

    protected static Func<SingletonKey, T> Creator;
    private static T instance;
    public static T Instance
    {
        get
        {
            if (instance == null) instance = Creator(Key);
            return instance;
        }
    }
}

public class MySingleton : Singleton<MySingleton>
{
    public string Name { get; set; }
    public static void Initialize()
    {
        Creator = (key) => new MySingleton(key);
    }
    protected MySingleton(SingletonKey key) : base(key)
    {
    }
}

As aulas em Java ou C# não são "de primeira classe". A parte estática de uma classe não pode ser herdada ou substituída por subclasses. Ver esta resposta para mais detalhes. Além disso, você não tem um conceito de meta-classe.

Em idiomas como SmallTalk ou Ruby, você pode definir um novo metaclasse Singleton que define um método getInstance. Então você pode definir ClassA e ClassB sejam instâncias do Singleton metaclasse. Em seguida, ambas as classes expõem automaticamente um método getInstance que pode ser usado para criar instâncias objectA ou objectB. Isso não é legal? Bem, na prática, você não usa o Metaclass com frequência, e o Singleton é na verdade o único uso deles que faz sentido e que conheço.

Acredito que tentei alcançar algo semelhante ao IE aplique uma interface comum e o padrão de singleton em um grupo de classes. Esta foi minha solução:

// Common interface of my singleton classes
public interface IMySingletonClass
{
    string ValueGetter();

    void ValueSetter(string value);
}

// Generic abstract base class
public abstract class Singleton<T>: IMySingletonClass
{
    private static readonly object instanceLock = new object();
    private static T instance; // Derived class instance

    // Protected constructor accessible from derived class
    protected Singleton()
    {
    }

    // Returns the singleton instance of the derived class
    public static T GetInstance()
    {
        lock (instanceLock)
        {
            if (instance == null)
            {
                instance = (T)Activator.CreateInstance(typeof(T), true);
            }
            return instance;
        }
    }

    // IMySingletonClass interface methods
    public abstract string ValueGetter();

    public abstract void ValueSetter(string value);
}

// Actual singleton class
public class MySingletonClass : Singleton<MySingletonClass>
{
    private string myString;

    private MySingletonClass()
    {
        myString = "Initial";
    }

    public override string ValueGetter()
    {
        return myString;
    }

    public override void ValueSetter(string value)
    {
        myString = value;
    }
}

Aqui está um teste simples:

class Program
{
    static void Main(string[] args)
    {
        MySingletonClass r1 = MySingletonClass.GetInstance();
        Console.WriteLine("R1 value = {0}", r1.ValueGetter());
        r1.ValueSetter("Changed through R1");
        MySingletonClass r2 = MySingletonClass.GetInstance();
        Console.WriteLine("R2 value = {0}", r2.ValueGetter());

        Console.ReadKey();
    }
}

Observe que você pode remover facilmente a interface comum da classe genérica abstrata de singleton se precisar apenas do "modelo" básico.

Aqui minha implementação da herança singleton:

using System;
using System.Reflection;

namespace Mik.Singleton
{
    class Program
    {
        static void Main()
        {
            //You can not create an instance of class directly
            //Singleton1 singleton1 = new Singleton1();

            Singleton1 singleton1 = Singleton1.Instance;
            Singleton2 singleton2 = Singleton2.Instance;

            Console.WriteLine(singleton1.Singleton1Text);
            Console.WriteLine(singleton2.Singleton2Text);

            Console.ReadLine();
        }
    }

    public class SingletonBase<T> where T : class
    {
        #region Singleton implementation

        private static readonly object lockObj = new object();
        private static T _instance;

        protected SingletonBase() { }

        public static T Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (lockObj)
                    {
                        if (_instance == null)
                            _instance = CreateInstance();
                    }
                }
                return _instance;
            }
        }

        private static T CreateInstance()
        {
            ConstructorInfo constructor = typeof(T).GetConstructor(
                            BindingFlags.Instance | BindingFlags.NonPublic,
                            null, new Type[0],
                            new ParameterModifier[0]);

            if (constructor == null)
                throw new Exception(
                    $"Target type is missing private or protected no-args constructor: {typeof(T).FullName}");
            try
            {
                T instance = constructor.Invoke(new object[0]) as T;

                return instance;
            }
            catch (Exception e)
            {
                throw new Exception(
                    "Failed to create target: type=" + typeof(T).FullName, e);
            }
        }

        #endregion Singleton implementation
    }

    public class Singleton1 : SingletonBase<Singleton1>
    {
        private Singleton1() { }

        public string Singleton1Text { get; } = "Singleton1Text value";
    }

    public class Singleton2 : SingletonBase<Singleton2>
    {
        private Singleton2() { }

        public string Singleton2Text { get; } = "Singleton2Text value";
    }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top