【问题标题】:Any good way for getting a single instance instead of using Singleton (in C#)获取单个实例而不是使用 Singleton(在 C# 中)的任何好方法
【发布时间】:2011-04-25 17:17:53
【问题描述】:

我曾经使用singleton获取单个实例,但由于测试中的一些缺点而被其他人否认。有什么缺点? 有没有其他获取全局实例的好方法?


那么我可以在应用程序中只创建一个单例类 Global 作为全局集线器吗?这样我就可以将任何其他类的单个实例放在 Global.Instance 中。

【问题讨论】:

  • @ChrisWue:+1 完美查找!
  • @Chris:这是一个相关的讨论,但不是重复的。您的链接是关于“为什么单身人士不好”,而这个问题询问使用什么来代替。

标签: c# singleton


【解决方案1】:

好的,为什么单身人士不好,你可以在这里阅读:What is so bad about singletons?

专门用于测试:典型的单例模式如下所示

class MyClass
{
    private static MyClass m_Instance = new MyClass();
    public static MyClass Instance
    {
        get { return m_Instance; }
    }
}

现在假设您必须运行一大堆涉及MyClass.Instance 的测试。这个类带有一个状态,你通常需要有一种方法在测试之间重置它,以便每个测试都可以从一个干净的初始状态开始。现在您可以添加一个Reset() 方法,但这意味着您将代码添加到您的类中只是为了能够测试它,这是不可取的。

改用什么:好吧,单例本身并不是什么坏事。它导致的问题是,这样编写代码很容易:

class SomeClass
{
    public void SomeMethod()
    {
        ...
        MyClass.Instance.DoSomething();
    }
}

现在你已经创建了一个对你不能轻易破坏的单例实例的隐式依赖。解决这个问题的一种方法是通过依赖注入(已经提到过):

class SomeClass
{
    public SomeClass(MyClass myClass)
    {
        m_MyClass = myClass;
    }

    public void SomeMethod()
    {
        ...
        m_MyClass.DoSomething();
    }
}

你可以这样做:

var someClass = new SomeClass(MyClass.Instance);

在你的程序中和

var someClass = new SomeClass(new MyClass());

用于测试。

所以单身人士并不是那么糟糕,有时是适合工作的正确工具,但是您需要注意如何使用它们。

【讨论】:

  • 我不认为单例和 DI 矛盾。我认为它们可以很容易地结合起来。单例应该是我在程序启动时创建的选项,如果你使用界面,我看不到任何可测试性问题。
  • 我从来没有说过他们矛盾。我特别指出,将 DI 与单例结合使用是您真正想要做的(大部分时间)。
  • 我 90% 同意你的回答,除了这句话:更好的方法是通过依赖注入(已经提到过):
  • 好的,记住“总是有不止一种解决方案”,我改写了一下 ;)
【解决方案2】:

在我看来,可测试性是主要困难,为了在一定程度上解决这个问题,我们有统一容器。
检查 Unity 容器http://unity.codeplex.com/

【讨论】:

    【解决方案3】:

    我没有看到任何可测试性和单例的问题。我也看不出 DI 是如何替代它们的。

    【讨论】:

    • 请检查第一个答案中的#3 项! stackoverflow.com/questions/137975/…
    • @System.Exception:“它们本质上会导致代码紧密耦合。这使得在许多情况下在测试中伪造它们相当困难。”
    • 更新了我的答案! ;-) 正如您在评论中发布的那样,测试“很难”!
    • 因为在我的情况下单例总是与工厂模式一起使用,我不同意这一点。
    【解决方案4】:

    考虑改用所谓的“环境上下文”模式。这个例子取自 Mark Seeman 的优秀 .NET 中的依赖注入一书。这种模式使您可以轻松使用单例,并且还具有可测试性。这里的重点是存在所谓的良好“默认值”(即示例中显示的 DefaultTimeProvider)。要考虑的另一件事是不要滥用这种模式,在大多数情况下,您可以将不同的依赖项注入到类的构造函数中。这种模式应该只用于贯穿整个应用程序的关注点。

    public abstract class TimeProvider
    {
        private static TimeProvider current;
        static TimeProvider()
        {
            TimeProvider.current = new DefaultTimeProvider();
        }
        public static TimeProvider Current
        {
            get { return TimeProvider.current; }
    
            set
            {
                if (value == null)      
                {         
                    throw new ArgumentNullException("value");    
                }              
                TimeProvider.current = value; 
            }
        }
        public abstract DateTime UtcNow { get; }
        public static void ResetToDefault()
        {
            TimeProvider.current = new DefaultTimeProvider();
        }
    }
    

    【讨论】:

    • 如上面接受的答案中所述,这与具有重置方法的单例有何不同(除了更好的名称)?
    • 这里提到的重置只是重置单例的“状态”。这种模式更具可插拔性,即您可以将其替换为其他实现。因此,即使它具有单例的易用性,也可以有多个不同的实现来进行测试等等。
    猜你喜欢
    • 2018-02-07
    • 1970-01-01
    • 2020-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-23
    • 1970-01-01
    相关资源
    最近更新 更多