单例模式的替代方案是什么?

7

我一直以来都是使用ASP.NET和C#作为Web开发人员,现在想通过使用最佳实践来提高自己的技能。

我有一个网站。我想只加载一次设置,然后在需要的地方引用它。因此,我进行了一些研究,发现50%的开发人员似乎使用单例模式来实现这一点。而另外50%的开发人员则反对单例。他们都不喜欢单例,推荐使用依赖注入。

为什么单例模式不好?加载网站设置的最佳实践是什么?它们应该只在需要时加载并引用吗?如果我想使用依赖注入来实现这一点(我对此还很陌生),应该怎么做?有没有样例可以推荐给我?我还想看到一些针对我的情况的单元测试代码。

谢谢 Brendan


可能是是否有任何可行的 GOF 单例模式替代方案?的重复。 - AlikElzin-kilaka
4个回答

6
通常情况下,我会避免使用单例模式,因为这会使得应用程序的单元测试变得更加困难。单例模式很难在单元测试中进行模拟,因为它们的本质是始终返回同一个实例,而不能轻易地为单元测试配置不同的实例。但是,强类型的配置数据是我所做的一个例外。通常配置数据都是相对静态的,另一种方法则需要编写大量代码来避免使用框架提供的静态类来访问 web.config。
有几种不同的方法可以使用单例模式,仍然可以让你进行单元测试。其中一种方法(如果你的单例模式没有延迟读取 app.config 的话,可能还有另一种方法)是在单元测试项目中提供一个默认的 app.config 文件,为你的测试提供所需的默认值。你可以使用反射来替换任何特定值,在你的单元测试中按需修改。通常情况下,我会配置一个私有方法,以便在测试设置时删除私有单例实例,如果我确实需要为特定测试进行更改的话。
另一种方法是不直接使用单例模式,而是创建一个接口,由单例类来实现。你可以手动注入接口,并将其默认为单例实例,如果提供的值为 null,则使用单例实例。这样可以创建一个模拟实例,将其传递给正在测试的类进行测试,在实际代码中使用单例实例。基本上,每个需要它的类都维护一个对单例实例的私有引用并使用它。我更喜欢这种方法,但由于单例将被创建,你可能仍然需要默认的 app.config 文件,除非所有的值都是惰性加载的。
public class Foo
{
    private IAppConfiguration Configuration { get; set; }

    public Foo() : this(null) { }

    public Foo( IAppConfiguration config )
    {
        this.Configuration = config ?? AppConfiguration.Instance;
    }

    public void Bar()
    {
         var value = this.Config.SomeMaximum;
         ...
    }
}    

很抱歉回复晚了。关于将配置数据作为异常的有趣评论。我通常采取相反的方法。我尝试将我的运行时配置实现为接口,并在启动时将它们注入应用程序中。这样,我的程序只依赖于接口本身,这些接口可以自由更改或模拟进行测试。在启动时,我确实需要使用ConfigurationManager读取对象,但这是唯一知道该静态类的地方。 - Kerry
这也是我采用的方法,尽管我的配置界面在 DI 容器中注册为单例以获得最佳效果。然而,这仍然是我感到舒适的 Singleton 实现的唯一示例。 - tvanfosson

2

我也想进行单元测试,但不知道如何对单例模式进行单元测试? - Brendan Vogt

1

设计模式可以是非常棒的东西。不幸的是,单例似乎像一个明显的问题,并且在许多情况下被认为是反模式(它促进了不良实践)。令人惊异的是,大多数开发人员只知道一种设计模式,那就是单例。

理想情况下,您的设置应该是高级位置的成员变量,例如拥有您正在生成的网页的应用程序对象。然后页面可以向应用程序请求设置,或者应用程序可以在构建页面时传递设置。


在我参加的设计模式课程中,单例模式是首先教授的。这可能是为什么每个人都记得它的原因。 - Brian Hooper
3
@Brian: 这种模式在http://sites.google.com/site/steveyegge2/singleton-considered-stupid中有描述。 - wRAR
你有描述的代码示例吗?或者链接? - Brendan Vogt
你可以尝试使用这里描述的内容:http://support.microsoft.com/kb/309018。 - DanDan

0
一种解决这个问题的方法是将其视为一个DAL问题。
无论是哪个类/网页等需要使用配置设置的地方,都应该声明对IConfigSettingsService(工厂/仓储/你喜欢称之为什么的东西)的依赖。
private IConfigSettingsService _configSettingsService;

public WebPage(IConfigSettingsService configSettingsService)
{
    _configSettingsService = configSettingsService;
}

所以你的类会得到这样的设置:

ConfigSettings _configSettings = _configSettingsService.GetTheOnlySettings();

ConfigSettingsService的实现将具有一个依赖项,即Dal类。那么Dal如何填充ConfigSettings对象呢?谁在乎。

  • 也许它会每次从数据库或.config xml文件中填充ConfigSettings。

  • 也许它会在第一次这样做,但随后为后续调用填充静态_configSettings。

  • 也许它会从Redis获取设置。如果有什么东西表明设置已更改,则dal或其他外部组件可以更新Redis。(如果您有多个应用程序使用设置,则此方法将非常有用。

无论它做什么,您唯一的依赖项是非单例服务接口。这很容易模拟。在测试中,您可以让它返回带有任何内容的ConfigSettings)。

实际上,更可能的是MyPageBase具有IConfigSettingsService依赖项,但它同样可以是Web服务、Windows服务、MVC somewhatsit,或者以上所有内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接