【问题标题】:DI/IoC with a Singleton Logger class带有 Singleton Logger 类的 DI/IoC
【发布时间】:2012-08-21 19:15:05
【问题描述】:

我们使用内部简单的 Logger 类来执行应用程序的日志记录任务 (.NET 3.5)。

记录器代码相当陈旧,其设计与此类似:

public class Logger : ILogger
{
     private ILogger instance;

     private static ILogger Instance
     { 
         // Initialized on first use.
         get { return instance; }
     }

     public static void Debug(string msg)
     {
           instance.Debug(msg);
     }

     public static void Error(string msg)
     {
           ....
     }
}

实例本身在第一次使用时被初始化(懒惰地)。

根据其严格的“书本”实现,这不是一个单例,但是,从所有调用代码对此类的访问是静态访问

出于测试目的和其他架构原因,我希望能够用其他东西(注入)替换内部实例

我怎样才能轻松做到这一点?我们目前没有使用任何 IoC 容器,但我不想将 setter 暴露给 Instance 属性,因为这会破坏整个 Singleton 式设计。

关于如何为此提出解决方案的任何建议?

【问题讨论】:

  • would not want to expose a setter to the Instance property since that would defeat the whole Singleton like design。不,它不会。您仍然可以使用默认实现对其进行初始化。 setter 只允许您在需要时/如果需要时替换它。

标签: c# .net design-patterns singleton


【解决方案1】:

考虑使用Fakes Framework 进行测试。您可以使用类似这样的方式对静态方法的调用进行存根

ShimLogger.Instance = () => new LoggerMock();

如果是 .net 3.5,您可以使用 Moles Framework 来存根静态方法调用。配置代码如下所示:

MLogger.Instance = () => new LoggerMock();

这需要将静态方法Instance 公开,但在此配置之后,每次调用静态方法都会返回您的模拟实例。

【讨论】:

【解决方案2】:

确实,二传手听起来不是一个好选择。

相反,我会考虑两种可能的方法。一、显式配置方法:

public class Logger : ILogger {

  public void ConfigureLogger( ILogger logger ) {
     this.instance = logger;
  }

}

这种方法的一个优点是意图很明确,而且您必须以显式方式调用此方法。

另一种选择是允许在您的配置中传递一种类型的记录器:

<appSettings>
    <add key="loggerType" value="The.Type.From, Some.Assembly" />
</appSettings>

然后,在您的Logger 类中重写初始化例程,以便如果存在配置参数,您更喜欢配置中提供的类型而不是默认类型。

这种方法的一个优点是您可以使用配置更改重新配置客户端,而无需更改代码。

无论如何,IoC 容器不会咬人。介绍一个,因为它会带来长期回报。

【讨论】:

  • 这是我已经想到的(大致),但这对于单元测试(配置文件)来说有点麻烦,除非它可以换成另一种配置机制。
  • 看前一种方法。坦率地说,您可以同时提供两者。因此,在您的SetUp 中,您只需调用ConfigureLogger,但在生产中,您可以使用声明性配置来交换记录器。我仍然想知道为什么您不使用 log4net。它可以以编程方式或声明方式进行配置,而且它还有很多您在实现中可能缺少的其他功能。
  • 因为我有大量的代码已经依赖于这种机制。这就是我不使用其他记录器的原因。
  • 嗯,您现有的类可以作为任意记录器的外观。您无需更改客户端代码中的任何内容,只需在内部,在 Logger 类中,您就可以开始使用 log4net(或您选择的任何记录器)。
【解决方案3】:

我不会自己动手。我使用企业库来满足我几乎所有的日志记录需求。它适用于桌面和 asp.net 项目。 Asp.net 可能会有点问题,因为您必须处理服务器上的安全问题,但我已经做到了。

http://entlib.codeplex.com/

人们也喜欢 Log4Net,但我从未使用过它,所以我无法评论它。

【讨论】:

  • 我有一个庞大的代码库,已经有无数次调用我们自己的记录器。从头开始重新设计,我确实会选择一个开源的 EL 记录器(在评估了一些之后)。
【解决方案4】:

我会使用 Logger 修改代码。不要通过 Logger.Instance 访问记录器,而是将所需的记录器实例传递到对象中。然后在您的工厂和/或组合根中,您将 Logger.Instance 作为生产代码中记录器的源传递,并且在您的单元测试中使用模拟记录器很容易。

public class Foo
{
  private readonly ILogger logger;

  public Foo(ILogger logger)
  {
    if (logger == null)
      throw new ArgumentNullException("logger");
    this.logger = logger;
  }

  public void Func()
  {
    try
    {
      // do something
    }
    catch (Exception ex)
    {
      // call the provided logger dependency
      this.logger.WriteError(ex);

      // not the static singleton property
      Logger.Instance.WriteError(ex);
    }
  }
}

【讨论】:

    【解决方案5】:

    另一个想法是为您的Instance 属性创建一个internal 设置器,并使用InternalsVisibleTo 属性使内部设置器对您的测试程序集可见。请注意,如果包含您的记录器的程序集是强命名的,那么您必须在InternalsVisibleTo 属性中指定PublicKey。显然,如果您的记录器位于自己的程序集或大多数开发/日志记录未发生的某种基础设施程序集中,这将是最有帮助的(在不让其他开发人员意外或故意将 Instance 设置为其他东西的意义上) .

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多