【问题标题】:How to abstract a singleton class?如何抽象一个单例类?
【发布时间】:2013-05-25 01:43:00
【问题描述】:

这就是我编写单例类的方式。

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() { .... }
}

如何创建可重用的单例模式?

单例模式存在以下挑战。

  • 构造函数是privateprotected
  • 基类不能实例化继承类。所以你可以重用一个通用的摘要MyAbstractSingletonClass
  • 它必须具有本地只读属性才能获取实例。

问题

我在许多类上都使用了这种模式,并且总是必须编写相同的代码。当我需要一个单例时,如何编写可以重用的东西?

【问题讨论】:

  • 我想了解您在这里要完成的工作。您是否希望基类了解派生类并将其返回,或者您是否希望基类只是“知道”以某种方式返回从它派生的唯一类?你可以向基类添加一个名为Initialize(string type) 的静态函数,或者告诉基类用什么来初始化单个实例吗?稍微澄清一下您要做什么以及您的限制是什么会有所帮助。
  • @Gjeltema 我编辑我的问题。希望对您有所帮助。
  • 是的,确实如此,但 BTownTKD 打败了我,让我输入了答案。 :)

标签: c# design-patterns singleton


【解决方案1】:

真正的解决方案是从 BTownTKD 的方法开始,但通过 Activator.CreateInstance 方法对其进行扩充,该方法允许您的子类保留私有构造函数。

父类

public abstract class SingletonBase<T> where T : SingletonBase<T>
{
    private static readonly Lazy<T> Lazy =
        new Lazy<T>(() => Activator.CreateInstance(typeof(T), true) as T);

    public static T Instance => Lazy.Value;
}

儿童班

public sealed class MySingleton : SingletonBase<MySingleton>
{
    private MySingleton() { }
}

Full Implementation Example Here

【讨论】:

  • 我不确定为什么这不是被接受的答案,或者为什么这没有收到任何赞成票。对于这个解决方案,我能想到的唯一警告是私有无参数构造函数的存在只能在运行时而不是在编译时强制执行。然而,除此之外,这是迄今为止列出的最强大的解决方案。
  • 这个答案很好,除了你应该使用 Lazy 而不是 ThreadLocal。如果某事只是“每个线程单例”,那么它就不是真正的“单例”。
【解决方案2】:

您可以使用self-referencing generic type constraint 和“new()”类型约束的组合来实现此目的。

“new”约束确保任何子类始终具有无参数构造函数,因此_instance = new T(); 将始终有效。

自引用类型约束确保“Instance”静态属性始终返回正确的类型;不是“基础”类型。你的单例基类看起来像这样:

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!
}

当然,如果你想让你的单例是通用的,你也应该稍微改变你的“创建单例实例”代码,使用“double-check lock”模式,或者Lazy类来实现线程安全。

最大的警告:如果你使用这个方法,“new()”约束几乎可以确保你的类总是有一个公共的、无参数的构造函数。这意味着您的最终用户可以随时调用new MyChildSingleton(),如果他们真的想要,完全绕过您的单例实例。您的单身人士将是“按照惯例”,而不是严格执行。要解决这个问题需要更多的工程。在上述场景中,约定似乎是您应该将静态实例命名为“Default”而不是“Instance”。这巧妙地传达了这样一个事实,即您的类提供了一个“建议的”单例实例,但使用它在技术上是可选的。

我做了一些尝试来严格执行单例模式,最终结果是使用反射来手动调用私有构造函数。你可以看到我的完整代码尝试here

【讨论】:

  • 在运行时限制构造函数调用实际上非常简单(不确定在编译中是否可行)。您所做的只是在 SingletonBase 中添加一个受保护的构造函数,如果 _instance 不为空,它会引发异常。即使构造函数是从外部调用的第一件事,也会抛出异常。
  • 这些类型不是单例。你必须对这些类型有一个公共构造函数来满足通用约束,所以任何人都可以创建它们的新实例,使它们不是单例。
【解决方案3】:

在基类中实现单例模式是一种值得商榷的做法。

但是,您可以实现其他可能适合您要完成的任务的创建设计模式。例如,看看Abstract Factory

【讨论】:

  • 您最初的陈述不正确。是可以做到的。
  • @bopapa_1979 我将答案编辑得更准确。
  • 我将反对票改为赞成票。感谢您的出色编辑。
【解决方案4】:

我相信通过@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; } }

}

【讨论】:

  • 假设发起者想要一个多线程、线程安全的实现,并在派生类型上使用 PRIVATE 构造函数,那么这是最好的答案。
【解决方案5】:

添加到 BTownTKD 的答案,在运行时限制构造函数调用实际上非常简单(在编译中不确定可能)。您所做的只是在 SingletonBase 中添加一个受保护的构造函数,如果 _instance 不为空,它会引发异常。即使构造函数是从外部调用的第一件事,也会抛出异常。

我设法在单例基础上应用了这项技术,并使其变得惰性和线程安全,如下所述: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;
        }
    }
}

我必须说我没有做过密集的多线程测试,但正如一些人已经说过的,你总是可以使用旧的双重检查技巧。

【讨论】:

  • 谢谢你!我发现我必须稍微调整以防止手动实例化。 IsInstanceCreated = true 必须在 new S() 之前设置,如果 !IsInstanceCreated 则必须抛出 InvalidOperationException。否则,它仅在创建单例后才有效,但如果首先手动实例化该类则无效。
  • @JamiesonRhyne 我们都需要:WillInstanceCreate = true 设置在 new S() 之前和 IsInstanceCreated = true 之后。必须抛出异常if (!WillInstanceCreate || IsCreated)
【解决方案6】:

我建议的例子:

基类

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();

有关更多信息,请参阅我在this link 中的答案来源

【讨论】:

  • 你的类型约束应该是 where T : SingletonBase,而不是 where T : class。
【解决方案7】:

你是对的——就目前的情况而言,你无法做到这一点。但是您可以使用泛型来处理它,请注意,使用这种方法您将获得每个唯一派生类型的一个单例实例

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() { }
    }

}

【讨论】:

  • 您的所有派生类必须有一个公共构造函数,这意味着任何想要创建新实例的人都可以创建新实例,这意味着它们不是单例,因为可以有多个实例其中。
【解决方案8】:

我最近对一个相关问题提出了这个答案:

https://stackoverflow.com/a/20599467

使用此方法,基类管理派生类的所有实例的创建,因为所有派生类构造函数都需要一个只有基类可以提供的对象,并且不需要无参数构造函数的限制。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-20
  • 1970-01-01
相关资源
最近更新 更多