记录为装饰器与dependency injection – 如果我需要在类中登录,该怎么办?

(我最初在这篇评论中提到了这个问题,但Mark Seemann要求我创建一个新问题。)

我正在开始一个新的应用程序(.NET Core,如果这很重要),现在我正在尝试决定如何准确地进行日志记录。

普遍的共识似乎是日志记录是一个跨领域的问题,因此记录器不应该直接注入应该记录的类。

通常,有一个例如下面的类如何这样做:

public class BadExample : IExample { private readonly ILogger logger; public BadExample(ILogger logger) { this.logger = logger; } public void DoStuff() { try { // do the important stuff here } catch (Exception e) { this.logger.Error(e.ToString()); } } } 

相反,具有业务逻辑的类不应该知道记录器( SRP ),并且应该有一个单独的类来执行日志记录:

 public class BetterExample : IExample { public void DoStuff() { // do the important stuff here } } public class LoggingBetterExample : IExample { private readonly IExample betterExample; private readonly ILogger logger; public LoggingBetterExample(IExample betterExample, ILogger logger) { this.betterExample = betterExample; this.logger = logger; } public void DoStuff() { try { this.betterExample.DoStuff(); } catch (Exception e) { this.logger.Error(e.ToString()); } } } 

每当需要IExample ,DI容器返回一个LoggingBetterExample实例,它使用BetterExample (包含实际的业务逻辑)。

这种方法的一些来源:

Mark Seemann的博客文章:

  • 装饰器和拦截器的仪表
  • dependency injection是松散耦合

博客文章和史蒂文的回答:

  • 同时……在我的架构的命令端
  • 温莎 – 从容器中拉出瞬态物体

我的问题:

显然, LoggingBetterExample方法只有在日志可以在实际类之外完成时才有效。
(如上例所示:捕获BetterExample从外部抛出的任何exception)

我的问题是我想在实际的类中记录其他内容。
Mark Seemann 在这里怀疑 ,如果有人需要这样做,可能有问题的方法做得太多了。

正如我之前所说,我正处于新应用程序的规划阶段,因此我没有太多代码可以显示,但我现在正在考虑的用例是这样的:

我的应用程序将有一个包含一些可选值的配置文件。
用户可能决定省略可选值,但这是一个重要的决定。
因此,我想在缺少某些可选值时记录警告,以防万一发生错误。
(省略这些值是完全正常的,所以我不能只抛出exception并停止)

这意味着我将有一个读取配置值的类,需要做这样的事情(伪代码):

 var config = ReadConfigValues("path/to/config.file"); if (config.OptionalValue == null) { logger.Warn("Optional value not set!"); } 

无论ReadConfigValues是在这个类中还是在另一个类中,我都认为这个类不会违反SRP。

当我无法使用装饰器登录实际类时,是否有比注入记录器更好的解决方案?

我知道我可以读取内部类中的配置文件,但检查装饰器中的值(并记录警告)。 但IMO检查值是业务逻辑而不是基础结构,所以对我来说它属于读取配置文件的同一个类。

检查值是业务逻辑而不是intfastructure,所以对我来说它属于读取配置文件的同一个类。

显然,我不太了解你的域名,不足以质疑该断言的真实性,但是日志记录是域模型的一部分听起来很奇怪。 无论如何,为了论证,让我们假设情况就是这样。

但是,情况并非如此,那就是读取配置文件是域逻辑。 虽然从文件中读取和操作数据很容易成为域逻辑,但读取文件是I / O.

应用程序体系结构中最常用的控制反转方法是采用端口和适配器架构 。 这种架构的全部意义在于将域模型与I / O和其他非确定性源分离。 海报示例将展示如何将域模型与其数据库访问分离,但文件访问也完全属于该类别。

在这种特殊情况下,这应该意味着你无论如何都需要一些IConfigurationReader接口。 这意味着您可以应用装饰器:

 public class ValidatingConfigurationReader : IConfigurationReader { private readonly IConfigurationReader reader; private readonly ILogger logger; public ValidatingConfigurationReader(IConfigurationReader reader, ILogger logger) { this.reader = reader; this.logger = logger; } public MyConfiguration ReadConfigValues(string filePath) { var config = this.reader.ReadConfigValues(filePath); if (config.OptionalValue == null) { this.logger.Warn("Optional value not set!"); } return config; } } 

即使基础文件读取IConfigurationReader实现属于某个I / O层,也可以在域模型中实现此ValidatingConfigurationReader类。