【问题标题】:Which pattern to use for logging? Dependency Injection or Service Locator?使用哪种模式进行日志记录?依赖注入还是服务定位器?
【发布时间】:2011-02-10 13:43:48
【问题描述】:

考虑这种情况。我有一些业务逻辑,不时需要写入日志。

interface ILogger
{
    void Log(string stuff);
}

interface IDependency
{
    string GetInfo();
}

class MyBusinessObject
{
    private IDependency _dependency;

    public MyBusinessObject(IDependency dependency)
    {
        _dependency = dependency;
    }

    public string DoSomething(string input)
    {
        // Process input
        var info = _dependency.GetInfo();
        var intermediateResult = PerformInterestingStuff(input, info);

        if (intermediateResult== "SomethingWeNeedToLog")
        {
            // How do I get to the ILogger-interface?
        }

        var result = PerformSomethingElse(intermediateResult);

        return result;
    }
}

如何获得 ILogger 界面?我看到了两种主要的可能性;

  1. 在构造函数上使用依赖注入传递它。
  2. 通过单例服务定位器获取它。

您更喜欢哪种方法,为什么?还是有更好的模式?

更新: 请注意,我不需要记录所有方法调用。我只想记录一些在我的方法中可能发生也可能不会发生的(罕见)事件。

【问题讨论】:

  • 第三个选项是属性注入(使用属性),这可以避免你的构造函数混乱

标签: c# design-patterns dependency-injection service-locator


【解决方案1】:

我个人是两者兼而有之。

这是我的约定:

  • 来自静态上下文 - 服务位置
  • 从实例上下文 - 依赖注入

我觉得这给了我可测试性的正确平衡。我发现针对使用服务位置的类设置测试比使用 DI 更难,所以这就是为什么服务位置最终成为例外而不是规则的原因。不过,我对它的使用是一致的,所以不难记住我需要编写什么类型的测试。

有些人担心 DI 会使构造函数变得混乱。我不觉得这是个问题,但如果你有这种感觉,有很多使用 DI 的替代方案,但要避免构造函数参数。以下是 Ninject 的 DI 方法列表: http://ninject.codeplex.com/wikipage?title=Injection%20Patterns

您会发现大多数 Inversion of Control 容器都具有与 Ninject 相同的功能。我选择展示 Ninject 是因为他们有最简洁的样本。

希望这会有所帮助。

编辑:为了清楚起见,我使用 Unity 和 Common Service Locator。我有一个用于 DI 的 Unity 容器的单例实例,而 IServiceLocator 的实现只是该单例 Unity 容器的包装器。这样我就不必做任何类型映射两次或类似的事情了。

我也没有发现 AOP 除了跟踪之外特别有用。我更喜欢手动记录,因为它更清晰。我知道大多数 AOP 日志框架都具备这两种功能,但大多数时候我不需要前者(AOP 的生计)。当然,这只是个人喜好。

【讨论】:

  • 谢谢,这很符合我的感受!
【解决方案2】:

记录器显然是您的业务逻辑所依赖的服务,因此应该像处理IDependency 一样将其视为依赖项。在构造函数中注入记录器。

注意: 尽管 AOP 被提及为 注入日志记录的方式,但我不同意它是这种情况下的解决方案。 AOP 非常适合执行跟踪,但永远不会成为将日志记录作为业务逻辑一部分的解决方案。

【讨论】:

  • 记录器显然是您的业务逻辑所依赖的服务 - 我不同意这一点。您的业​​务逻辑绝不应该依赖于日志记录,并且您的应用程序应该在没有它的情况下也能正常工作。但是,您的调试会话...
  • @ThorkilHolm-Jacobsen 在 OP 的情况下是:不时需要写入日志的业务逻辑。我同意并非总是如此。但我不同意您的 业务逻辑不应依赖于日志记录 。我曾多次遇到这样的业务需求。
【解决方案3】:

我的小经验法则:

  • 如果它在类库中,请使用构造函数注入或具有空对象模式的属性注入。

  • 如果它在主应用程序中,请使用服务定位器(或单例)。

我发现这在使用 log4net 时非常适用。您不希望类库访问可能不存在的东西,但在应用程序中,您知道记录器将存在,并且像 log4net 之类的库在很大程度上基于服务位置模式。

我倾向于认为日志记录是一种足够静态的东西,它并不真正需要 DI。我不太可能更改应用程序中的日志记录实现,尤其是因为那里的每个日志记录框架都非常灵活且易于扩展。当您的库可能需要由已经使用不同记录器的多个应用程序使用时,它在类库中更为重要。

当然是 YMMV。 DI 很棒,但这并不意味着一切都需要 DI。

【讨论】:

    【解决方案4】:

    也许这有点离题,但是当我们可以在课程的开头键入时,为什么我们还需要注入记录器:

    Logger logger = LogManager.GetLogger("MyClassName");
    

    Logger 在开发期间和以后的维护期间不会更改。现代记录器是高度可定制的,所以争论

    如果我想用数据库替换文本记录器怎么办?

    错过了。

    我不否定使用依赖注入,我只是好奇你的想法。

    【讨论】:

    • 完全同意。例如,log4net 可以配置为基本上记录到任何内容,您也可以编写自己的适配器。通过这种方式,您可以知道使用了哪个记录器,在注入时很难弄清楚
    • 我同意。我相信对于绝大多数应用程序来说,日志记录的依赖注入是多余的。通常,日志框架可以配置日志到哪里(以及更多),这为它们提供了您想要的灵活性。另外,我从未见过需要为测试模拟日志记录功能。例如,当您想通过配置中的命名空间更改日志级别时,DI 甚至会使您的生活变得更加复杂。日志框架如何知道它在哪个命名空间中? (有一些解决方案,但我觉得它们并不优雅)
    • 但是,我强烈建议您在创建库时使用 DI。这是 DI 和日志记录的一个常见用例
    【解决方案5】:

    我们将所有的 Logging/Tracing 切换为 PostSharp(AOP 框架)属性。为方法创建日志记录所需要做的就是为其添加属性。

    好处:

    • 轻松使用AOP
    • 清除关注点分离
    • 发生在编译时 -> 最小的性能影响

    查看this。

    【讨论】:

    • 好吧,很遗憾我不能在这里使用简单的 AOP 方法。我不想记录所有方法调用,只记录方法内部可能发生或不发生的一些事件。
    【解决方案6】:

    我更喜欢单例服务。

    依赖注入会使构造函数混乱。

    如果你可以使用AOP,那就最好了。

    【讨论】:

    • DI != 混乱的构造函数。
    • @Will:你能解释一下吗?
    • @Will 否决票似乎有点苛刻。如果使用构造函数注入,ILogger 接口确实会使构造函数“混乱”,而构造函数并不是真正的核心依赖项。
    • @andlju:我猜@Will 指的是(正确的)DI 不一定需要构造函数参数的事实。涉及设置属性或其他方法的 DI 同样有效。以下是 Ninject 的 DI 方法列表:ninject.codeplex.com/wikipage?title=Injection%20Patterns
    • -1,因为:1) Singleton 是一种反模式。 2)使构造函数混乱?解释你自己! 3) AOP 不能将日志记录作为业务逻辑的一部分。
    【解决方案7】:

    您可以派生另一种类型,例如LoggableBusinessObject 在其构造函数中接受一个记录器。这意味着您只传递将使用它的对象的记录器:

    public class MyBusinessObject
    {
        private IDependency _dependency;
    
        public MyBusinessObject(IDependency dependency)   
        {   
            _dependency = dependency;   
        }   
    
        public virtual string DoSomething(string input)   
        {   
            // Process input   
            var info = _dependency.GetInfo();   
            var result = PerformInterestingStuff(input, info);   
            return result;   
        }   
    }
    
    public class LoggableBusinessObject : MyBusinessObject
    {
        private ILogger _logger;
    
        public LoggableBusinessObject(ILogger logger, IDependency dependency)
            : base(dependency)
        {
            _logger = logger;
        }
    
        public override string DoSomething(string input)
        {
            string result = base.DoSomething(input);
            if (result == "SomethingWeNeedToLog")
            {
                 _logger.Log(result);
            }
        }
    }
    

    【讨论】:

    • 一种有趣的解决方案,但对于一个相对简单的任务来说,编码似乎有点太多了。它还只允许根据方法的结果记录事物(我意识到我的示例正是这样做的......)
    • +1 的解决方案允许我对 MyBusinessObject 进行单元测试,而无需模拟 ILogger。
    • 但是使用这种模式,你只能在调用基类的方法之前或之后登录。您仍然无法在方法中间登录。
    • @Martin,公平点,但是基于示例,提供的日志记录仅基于 DoSomething 的结果发生,因此上述解决方案非常适合。
    • 将字段/变量 _logger 更改为 readyonly 以便无法在 LoggableBusinessObject 中创建新实例
    【解决方案8】:

    DI 在这里可以很好地工作。另一件事是AOP。

    【讨论】:

      【解决方案9】:

      我不推荐这两种方法。最好使用面向方面的编程。日志记录是 AOP 的“hello world”。

      【讨论】:

      • 嗯,是的 - 但只有当您需要记录所有呼叫时(我已经这样做了)。但在这种情况下,我需要记录仅偶尔发生的事情(事件记录而不是调用记录,如果你明白我的意思的话)。
      • 如果偶尔这样做,我会说 DI 也将是矫枉过正。我有一个 Logger 的静态类成员并完成它。
      猜你喜欢
      • 1970-01-01
      • 2013-07-10
      • 2016-08-06
      • 2020-09-15
      • 2021-08-03
      • 1970-01-01
      • 2015-03-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多