【问题标题】:Using optional singletons in OOP?在 OOP 中使用可选单例?
【发布时间】:2015-05-28 04:38:41
【问题描述】:

我正在.NET 中编写 PCL,并且我有一个围绕 HttpClient 的包装类,它以多种不同的方法从 URI 加载 HtmlAgilityPack.HtmlDocument。它是无状态,所以我真的很想将其设为静态,因为在我看来,用new 实例化某些东西给人的印象是它包含状态。但是,我有几个我希望它继承的接口,所以它不能是静态的。这就是我想让它成为单例的地方。以下是代码中的一些 sn-ps:

public class ConcurrentClient : IAsyncClient<HtmlDocument>
{
    private static readonly ConcurrentClient _Instance = new ConcurrentClient();

    private ConcurrentClient() { }

    public static ConcurrentClient Instance
    {
        get { return _Instance; }
    }

    public HtmlDocument LoadUri(string uri)
    {
        return LoadUriAsync(uri).Result;
    }

    // ...

    public async Task<HtmlDocument> LoadUriAsync(string uri, 
        Encoding e, NetworkCredential creds, Action<HtmlDocument> prehandler)
    {
        // ...
    }
}

不过,我想知道是否应该将开头部分更改为:

private static readonly ConcurrentClient _SharedInstance = new ConcurrentClient();

public static ConcurrentClient SharedInstance
{
    get { return _SharedInstance; }
}

原因是我不太确定是否使用 Singleton 模式,主要是因为我很少在其他库中看到它使用它(可能是 WinRT 的Application.Current?),我认为它会鼓励用户我的 PCL 来编写耦合代码,因为在任何地方都调用 ConcurrentClient.Instance 比将其作为参数传递要容易得多。

但是,我确实想鼓励使用共享实例,因为除了上述原因之外,调用new ConcurrentClient() 没有什么意义,因为它所做的只是产生更多的内存开销。另外,我想不出更好的方法来使用不真正依赖状态的方法来实现继承。

【问题讨论】:

  • 你有一个陷阱:你正在使用单例。解释你的真正问题以及为什么你认为使用单例是解决方案会更好,我们可能会向你推荐一些不同的东西。
  • 我看不到任何澄清。要解决的真正问题或用例是什么?为什么你认为使用单例会解决它?为什么不使用具有状态但不可变类的不同设计来保证设计线程的安全?
  • 如果有一个公共构造函数,它根本就不是一个单例。是的,你有一个公开可用的共享实例,但你并没有阻止其他人被创建——所以我就把它留在那里。不要费心称它为单例。
  • 是否有公共构造函数根本不是OP的问题,他只是在问单例作为模式是否是一个好的选择。如果我们说没关系,我相信他会将构造函数设为私有并使其成为适当的单例

标签: c# oop design-patterns singleton


【解决方案1】:

您的 Singleton 已经实现了 2 个接口。真正的问题是,这个 Singleton 的依赖关系在哪里?为什么它们在那里?
如果答案是这些依赖项的存在是因为它们需要实现这些接口,那么我会说这是错误的。
进行 SOLID 设计的全部意义在于依赖于接口而不是任何具体的实现。因此,任何需要这两个接口中的任何一个的人都应该通过依赖注入获得这些接口。所以这意味着接口将通过它们的构造函数或通过方法调用中的额外参数、策略模式、...

另见:http://blogs.msdn.com/b/scottdensmore/archive/2004/05/25/140827.aspx

制作单例可能是有原因的,但根据您的解释,这并不那么清楚。

调查您在使用依赖注入方面的更多时间。如果您可以控制它,请进一步研究如何使用控制反转容器。

此外,当您可以通过 Singleton.Instance 访问它时,很容易忘记 DI 并将对象作为参数传递。

您忘记了单元测试。如果您将接口传递给您的类构造函数,您可以轻松地模拟这些接口并测试您的类功能。有了你的单身人士,你的班级真的需要那个单身人士。单元测试会更难。 当然 Instance 很容易访问,它是一个全球性的,而且由于人们一直回归到对对象编程的旧习惯,这就是它如此受欢迎的原因。

【讨论】:

  • 在我的库的其余部分中没有对单例的依赖,它是供公众使用的。当/如果我稍后在它之上构建时,我打算做的是准确地引用它一次,然后将它作为接口参数传递给我的其余方法。
  • 另外,我认为您可能误解了我帖子的最后一部分(在编辑之前):我是在暗示我对使用单例的恐惧之一,不是说“是的,让我们搞砸 DI,让我们的代码完全无法维护”。
  • 我还是不明白为什么你需要一个单例,你需要一个 IOC 容器。您只注册一次接口。每次需要解析依赖于先前注入的接口的另一个接口时,IOC 都会处理注入。无论这是否意味着一个新实例或同一个实例,都可以在 IOC 容器中进行配置。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-02
  • 1970-01-01
  • 2023-03-07
  • 1970-01-01
  • 2011-03-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多