Абстрактный базовый класс, чтобы заставить каждого полученных классов быть синглтоном
Вопрос
Как создать абстрактный класс, который должен заставить каждые полученные классы быть синглтоном? Я использую C #.
Решение
Когда вы хотите, чтобы проверить время компиляции, это невозможно. С проверкой выполнения вы можете сделать это. Это не красиво, но это возможно. Вот пример:
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()
{
}
}
Другие советы
Это не будет работать, потому что синглтон нуждается в созревении статического доступа, и которые не могут быть вынуждены.
Для Singletonimplemention + примеры см.: Реализация рисунка Singleton в C #
Singleton означает наличие частных конструкторов. Но вы знаете, что частные члены не могут быть унаследованы. В C ++ были шаблоны, поэтому вы можете создать синглтон из класса шаблона. В C # не шаблоны, поэтому вы должны написать свои собственные частные конструкторы для каждого Wingleton, который вы хотите.
Вот (уродливый) способ сделать это. Вероятно, это может быть упрощено и улучшено, но это мой первый пойти на него.
Идея состоит в том, чтобы сначала сделать базовый класс универсальным абстрактным классом (как упомянуто в комментариях выше), но параметр типа ограничен для получения самого базового класса. Это позволяет базовому классу обрабатывать экземпляр Singleton производного типа. Обратите внимание, что все полученные классы должны быть запечатаны, как и с любым классом Singleton.
Далее разрешен защищенный конструктор, но требуется принять экземпляр специального класса, Singletonkey, который является модифицированным Singleton. Полученные классы имеют доступ к определению класса Singletonkey, но базовый класс сохраняет частный контроль над единственным допустимым экземпляром и, таким образом, по конструкции всех производных объектов.
В-третьих, базовый класс должен будет позвонить конструктору полученного класса, но это немного сложно. Компилятор будет жаловаться, если вы попытаетесь вызвать производный конструктор ключа, поскольку он не гарантирован. Решение состоит в том, чтобы добавить статический делегат, который полученный класс должен инициализировать. Таким образом, любые полученные классы должны обеспечить простую метод инициализации. Этот метод инициализации следует явно называться, прежде чем пытаться получить доступ к экземпляру в первый раз в коде, или ошибка времени выполнения будет результатом.
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)
{
}
}
Классы в Java или C # не являются «первым классом». Статическая часть класса не может быть унаследована или переопределена подклассами. Видеть Этот ответ Больше подробностей. Кроме того, у вас нет понятия мета-класса.
На языках, таких как SmallTalk или Ruby, вы можете определить новый метакласс Singleton
который определяет метод getInstance
. Отказ Тогда вы можете определить ClassA
и ClassB
быть экземплярами Singleton
метакласс. Тогда оба класса автоматически подвергают методу getInstance
который можно использовать для создания экземпляров objectA
или objectB
. Отказ Разве это не круто? Ну, на практике вы часто не используете метакласс, а синглтон на самом деле является единственным использованием их, которые имеют смысл и что я знаю.
Я полагаю, что пытался достичь чего-то подобного, то есть обеспечить общий интерфейс и синглтон на группе классов. Это было мое решение:
// 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;
}
}
Вот простой тест:
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();
}
}
Обратите внимание, что вы можете легко удалить общий интерфейс от универсального абстрактного класса 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";
}
}