【问题标题】:Creating object "Catalog" for DI agnostic library为 DI 无关库创建对象“目录”
【发布时间】:2018-03-31 14:52:55
【问题描述】:

我正在开发一个内部库,供我所在公司的其他开发人员使用。我正在应用 SOLID 模式并遵循Dependency Inject (DI) “friendly” library 中描述的最佳实践。

我的最终用户将是不同应用程序的开发人员。其中一些是没有 DI 的复杂遗留应用程序,而另一些是具有 DI 和 TDD 的较新应用程序。

现在,我试图弄清楚如何从没有实现 DI 的遗留 ASP.NET Webforms 应用程序调用这个 DI 友好库,显然,我无法修改 250 多个 aspx 页面以支持构造函数注入因为它超出了我的项目范围。 (Yes, I have read Introducing an IoC Container to Legacy Code )

我的一个想法是为 Common Service Locator 创建一个静态全局包装器,以自动解决整个应用程序的依赖关系:

public static class GlobalResolver
{
    public static T Resolve<T>()
    {
        return ServiceLocator.Current.GetInstance<T>();
    }
}

这种方法的好处是我可以在我的组合根目录中使用任何 IoC 库(我目前使用 Unity)。我会像这样使用这个 GlobalResolver:

protected void OnClick(object sender, EventArgs e)
{
    IMailMessage message = MessageFactory.Create("Jack.Daniels@jjj.com", "John.Doe@jjj.com", "subject", "Body", true, MailPriority.High);
    GlobalResolver.Resolve<IMailer>().SendMail(message);
}

我喜欢这种方法,而且我认为它很干净,但是我公司的新手开发人员可能会对 GlobalResolver.Resolve&lt;IMailer&gt; 这条线感到困惑,所以我正在尝试看看是否有替代方法。

我想到的一件事是这样的:

public static class CommonCatalog
{
    public static IMailer Mailer => ServiceLocator.Current.GetInstance<IMailer>();
    public static IMailMessageFactory MessageFactory => ServiceLocator.Current.GetInstance<IMailMessageFactory>();
    public static IFtpSecureClientFactory FTPClientFactory => ServiceLocator.Current.GetInstance<IFtpSecureClientFactory>();

    // And so on...
}

然后像这样简单地使用它:CommonCatalog.Mailer.SendMail(message);。我公司的开发人员习惯于看到静态方法,我认为这种方法对他们来说可能是可取的。

我的问题是:

  1. 这是解决我问题的最佳方法吗?
  2. 我是否违反了任何最佳做法?
  3. 是否有描述 CommonCatalog 类的设计模式?它是“门面”还是“代理”?

TLDR:我公司的开发人员喜欢使用静态方法,但静态方法与 DI 和 SOLID 实践不兼容。有没有什么办法让人们误以为他们使用的是静态方法,但在幕后调用了 DI 代码?

【问题讨论】:

  • 我认为您已经回答了第二个问题;这种静态方法调用的方法违反了最佳实践。 DI in .NET 描述了您在第 5 章(反模式)和第 6 章(代码异味)中描述的大部分内容。但是,作为架构师,您需要考虑当前对开发人员和遗留软件的理解,所以除了您之外没有人可以回答第一个问题;这个问题非常主观,甚至可能使您的问题结束。您的 CommonCatalog 使用外观 (+)、服务定位器 (-) 和环境上下文 (-)。

标签: c# dependency-injection webforms ioc-container legacy-code


【解决方案1】:

如果您想避免使用Service Locator anti-pattern(您应该这样做,因为 - 这是一种反模式),那么带有GlobalResolver 的第一个选项是不可能的,因为it's definitely a Service Locator

服务目录更接近于我在DI-friendly libraries 的扩展文章中推荐的Facades,尽管我通常更喜欢没有聚合的对象目录。当我不知道如何命名对象时,总是让我感到不舒服,而像CommonCatalog 这样的名字似乎太没有意义了。

相反,我更喜欢使用文章中描述的 Fluent Builder 模式制作基于实例的 Facades,因为当您发现需要添加各种选项并切换到门面。

但是,如果您绝对需要,您可以为每个 Facade 添加一个静态方法。像这样的:

public static class Mailer
{
    public static IMailer Default
    {
        get { return new MailerBuilder().Create(); }
    }
}

如果可以想象实例具有 Singleton 生命周期,您可以改为使用 Singleton design pattern,如下一个示例所示。

您可以以相同的方式实现默认 MessageFactory,但这里使用 Singleton 设计模式:

public static class MailMessageFactory
{
    public static IMailMessageFactory Default { get } =
        new MailMessageFactoryBuilder().Create();
}

请注意,这也不使用服务定位器来实现。

不过,需要明确的是,这些门面的背后可以根据 SOLID 原则轻松实现,但调用代码仍然很难做到。

【讨论】:

  • 感谢您的意见。我在看你的书。爱它。干杯。
猜你喜欢
  • 2021-08-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-13
  • 2017-12-23
  • 1970-01-01
相关资源
最近更新 更多