Абстрактный базовый класс, чтобы заставить каждый производный класс быть синглтоном
Как мне сделать абстрактный класс, который заставит каждый производный класс быть Singleton? Я использую C#.
6 ответов
Если вы хотите включить проверку времени компиляции, это невозможно. С проверкой во время выполнения вы можете сделать это. Это не красиво, но возможно. Вот пример:
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#
Синглтон означает наличие частных конструкторов. Но вы знаете, что частные члены не могут быть унаследованы. В C++ были шаблоны, так что вы можете создать синглтон из класса шаблона. в C# нет шаблонов, поэтому вы должны написать свои собственные частные конструкторы для каждого нужного вам синглтона.
Вот (некрасивый) способ сделать это. Возможно, это можно было бы упростить и улучшить, но это мой первый шаг.
Идея состоит в том, чтобы сначала сделать базовый класс универсальным абстрактным классом (как упомянуто в комментариях выше), но параметр типа ограничен для получения из самого базового класса. Это позволяет базовому классу обрабатывать одноэлементный экземпляр производного типа. Обратите внимание, что все производные классы должны быть запечатаны, как и с любым классом-одиночкой.
Затем разрешен защищенный конструктор, но он должен принимать экземпляр специального класса SingletonKey, который является модифицированным синглтоном. Производные классы имеют доступ к определению класса 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";
}
}