Classe base abstrata para forçar cada classes derivadas a ser singleton
Pergunta
Como faço para fazer uma classe abstrata que deve forçar cada classes derivadas a ser singleton? Eu uso C#.
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";
}
}