作为装饰器与依赖注入进行日志记录——如果我需要在类内部进行日志记录,该怎么办



(我最初在这个评论中问了这个问题,但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类,但你不能访问/记录私人内容。第二种选择是使用分部类,并将日志记录语句放在一个单独的类中。

相关内容

  • 没有找到相关文章

最新更新