(我最初在这个评论中问了这个问题,但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的博客文章:
- 带装饰器和拦截器的仪器
- 依赖注入是松散耦合的
博客文章和SO答案由史蒂文:
- 与此同时。。。在我的体系结构的命令端
- Windsor-从容器中拉出瞬态物体
我的问题:
显然,LoggingBetterExample
方法只有在日志记录可以在实际类之外完成的情况下才有效
(就像上面的例子:捕获BetterExample
从外部抛出的任何异常)
我的问题是,我想在实际的类中记录其他事情
Mark Seemann在这里怀疑,如果有人需要这样做,也许这个方法做得太多了。
正如我之前所说,我正处于一个新应用程序的规划阶段,所以我没有太多代码要显示,但我现在正在思考的用例是这样的:
我的应用程序将有一个包含一些可选值的配置文件
用户可能决定省略可选值,但这是一个重要的决定
因此,当一些可选值丢失时,我想记录一个警告,以防出现错误
(省略这些值是完全可以的,所以我不能只是抛出一个异常并停止)
这意味着我将有一个类,它读取配置值,并需要执行类似的操作(伪代码):
var config = ReadConfigValues("path/to/config.file");
if (config.OptionalValue == null)
{
logger.Warn("Optional value not set!");
}
无论ReadConfigValues
是在这个类中还是在另一个类中,我都不认为这个类会违反SRP。
当我无法使用decorator在实际类之外进行日志记录时,有没有比注入记录器更好的解决方案
我知道我可以读取内部类中的配置文件,但在decorator中检查值(并记录警告)。但是IMO检查值是业务逻辑,而不是基础设施,所以对我来说,它属于读取配置文件的同一类。
检查值是业务逻辑,而不是intfaststructure,所以对我来说,它属于读取配置文件的同一类。
显然,我对你的域还不够了解,无法对该断言的真实性提出质疑,但日志记录是域模型的一部分,这对我来说听起来很奇怪。无论如何,为了论证起见,让我们假设情况确实如此。
不过,不应该是的情况,因为读取配置文件是域逻辑。虽然从文件中读取和操作数据很容易成为域逻辑,但读取文件就是I/O。
在应用程序体系结构中,控制反转最常见的方法是使用Ports&适配器体系结构。这种体系结构的全部目的是将域模型与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;
}
}
这个ValidatingConfigurationReader
类可以在域模型中实现,即使底层的文件读取IConfigurationReader
实现属于某个I/O层。
不要把SRP当回事,否则你最终会得到函数式编程。如果你害怕把日志语句放在里面会让你的课堂变得一团糟,那么你有两个选择。你已经提到的第一个是使用Decorator类,但你不能访问/记录私人内容。第二种选择是使用分部类,并将日志记录语句放在一个单独的类中。