39

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

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. 通过单例服务定位器获取它。

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

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

4

9 回答 9

18

我个人是两者兼而有之。

这是我的约定:

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

我觉得这给了我可测试性的正确平衡。我发现针对使用服务位置的类设置测试比使用 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 的生计)。当然,这只是个人喜好。

于 2010-04-21T13:34:08.763 回答
9

logger 显然是您的业务逻辑所依赖的服务,因此应该像使用IDependency. 在构造函数中注入记录器。

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

于 2010-04-21T13:50:49.943 回答
5

我的小经验法则:

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

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

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

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

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

于 2010-04-21T13:51:13.277 回答
5

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

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

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

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

错过了。

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

于 2016-10-07T14:11:31.483 回答
4

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

好处:

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

看看这个

于 2010-04-21T11:53:59.127 回答
2

我更喜欢单例服务。

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

如果你可以使用 AOP,那将是最好的。

于 2010-04-21T11:53:00.737 回答
2

您可以派生另一种类型,例如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);
        }
    }
}
于 2010-04-21T12:22:44.843 回答
0

DI 在这里工作得很好。另一件要看的事情是AOP

于 2010-04-21T11:52:34.933 回答
0

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

于 2010-04-21T11:52:59.777 回答