Как абстрагировать синглтон-класс?
Так я пишу свои синглтон-классы.
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() { .... }
}
Как создать шаблон Singleton, который можно использовать повторно?
Шаблоны синглтона представляют следующие проблемы.
- Конструктор
private
или жеprotected
, - Базовый класс не может создать экземпляр унаследованного класса. Таким образом, вы можете повторно использовать общий реферат
MyAbstractSingletonClass
, - У него должно быть локальное свойство только для чтения, чтобы получить экземпляр.
Эта проблема
Я использую этот шаблон на нескольких классах и всегда должен писать один и тот же код. Как я могу написать что-то, что используется повторно, когда мне нужен синглтон?
8 ответов
Этого можно добиться, используя комбинацию самопровозглашенного ограничения универсального типа и ограничения типа" new ()".
"Новое" ограничение гарантирует, что любой дочерний класс всегда будет иметь конструктор без параметров, поэтому _instance = new T();
всегда будет работать
Самостоятельно ссылающееся ограничение типа гарантирует, что статическое свойство "Экземпляр" всегда возвращает правильный тип; не "базовый" тип. Ваш базовый класс синглтона будет выглядеть примерно так:
public abstract class SingletonBase<T>
where T : SingletonBase<T>, new()
{
private static T _instance = new T();
public static T Instance
{
get
{
return _instance;
}
}
}
Ваши дочерние классы будут выглядеть так:
public class MyChildSingleton : SingletonBase<MyChildSingleton>
{
//Done!
}
Конечно, если вы хотите, чтобы ваш синглтон был универсальным, вы должны также немного изменить свой код "создания экземпляра синглтона", чтобы использовать шаблон " двойной проверки блокировки" или класс Lazy, чтобы сделать его поточно-ориентированным.
Большое предостережение: если вы используете этот метод, ограничение "new()" в значительной степени гарантирует, что ваш класс всегда будет иметь открытый конструктор без параметров. Это означает, что ваши конечные пользователи всегда могут просто позвонить new MyChildSingleton()
если они действительно хотят, полностью обходя ваш синглтонский экземпляр. Ваш синглтон будет "по соглашению", а не строго соблюдаться. Чтобы обойти это, потребуется немного больше техники. В приведенном выше сценарии, соглашение выглядит так, что вы должны назвать свой статический экземпляр "Default
" вместо "Instance
Msgstr "Это тонко передает тот факт, что ваш класс предлагает" предложенный "экземпляр синглтона, но использование его технически необязательно.
Я предпринял несколько попыток строго применить шаблон синглтона, и конечным результатом было использование отражения для вызова частного конструктора вручную. Вы можете увидеть мою полную попытку кода здесь.
Истинное решение начинается с подхода BTownTKD, но дополняет его методом Activator.CreateInstance, который позволяет вашим дочерним классам сохранять приватные конструкторы.
Родительский класс
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;
}
Детский класс
public sealed class MyChildSingleton : BaseSingleton<MyChildSingleton>
{
private MyChildSingleton() { }
}
Добавляя ответ BTownTKD, на самом деле довольно просто ограничить вызов конструктора во время выполнения (что не всегда возможно при компиляции). Все, что вам нужно сделать, это добавить защищенный конструктор в SingletonBase, который выдает исключение, если _instance не имеет значение null. Исключение будет выдано, даже если конструктор будет первым, что вызывается извне.
Мне удалось применить эту технику на базе синглтона, а также сделать ее ленивой и поточно-ориентированной, как описано здесь: http://csharpindepth.com/Articles/General/Singleton.aspx
Результат (с объяснением использования):
/// <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;
}
}
}
Должен сказать, что я не проводил интенсивного многопоточного тестирования, но, как уже говорили некоторые, вы всегда можете использовать старый трюк двойной проверки.
Простой ответ заключается в том, что вы не можете реализовать шаблон синглтона в базовом классе.
Тем не менее, вы можете реализовать другие шаблоны творческого проектирования, которые могут подходить для того, что вы пытаетесь достичь. Например, взгляните на абстрактную фабрику.
Вы правы - в настоящее время вы не можете этого достичь. Но вы можете подойти к нему с помощью обобщений, обратите внимание, что при таком подходе вы получите один экземпляр-одиночку для каждого уникального производного типа:
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() { }
}
}
Я полагаю, что с помощью решения @Buvy вы получите экземпляр для каждого потока, который может быть тем, что он намеревался. Вам придется немного его изменить, чтобы получить один экземпляр в потоках.
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; } }
}
Обобщенная, упорядоченная, реализация поточно-безопасного шаблона без блокировки:
public abstract class Singleton<T> where T : class, new() {
public static readonly T Instance = new T();
}
Статический конструктор исключен в пользу производительности, так как лень не требуется, и свойство для краткости заменяется на открытое поле.
Реализация
public sealed class Foo : Singleton<Foo> {
public void Bar() {
//...
}
}
использование
Foo.Instance.Bar();
Мой предложенный пример:
базовый класс
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;
}
}
использование
public class yourClass : SingletonBase<yourClass>
{
public yourMethod()
{
}
}
используйте свой синглтон-класс следующим образом:
yourClass.Instance.yourMethod();
для получения дополнительной информации см. мой источник ответа в этой ссылке
Я недавно предложил этот ответ на связанный вопрос:
С помощью этого метода базовый класс управляет созданием всех экземпляров производных классов, поскольку всем конструкторам производных классов требуется объект, который может предоставить только базовый класс, и нет необходимости в ограничении конструктора без параметров.