日志记录是否位于主要目的不记录的类中?

时间:2009-11-25 17:34:00

标签: logging decorator single-responsibility-principle decoupling solid-principles

这更像是一个理论问题。日志记录是否位于主要目的不记录的类中?

这是一个简单的界面,可用于对数字进行计算的任何内容。

public interface ICalculation { 
    public int calculate(int number); 
}

这是ICalculation接口的一个实现,它执行计算并执行一些日志记录。我相信这是一种非常务实的方法。除了构造函数接受我们通常不希望在计算域中看到的内容之外,内联日志记录可以说是非侵入式的。

public class ReallyIntenseCalculation : ICalculation {
    private readonly ILogger log;

    public ReallyIntenseCalculation() : this(new DefaultLogger()) {
    }

    public ReallyIntenseCalculation(ILogger log) {
        this.log = log;
        log.Debug("Instantiated a ReallyIntenseCalculation.");
    }

    public int calculate(int number) {
        log.Debug("Some debug logging.")
        var answer = DoTheDirtyWork(number);
        log.Info(number + " resulted in " + answer);
        return answer;
    }

    private int DoTheDirtyWork(int number) {
        // crazy math happens here
        log.Debug("A little bit of granular logging sprinkled in here.");
    }
}

从ReallyIntenseCalculation中删除所有日志记录代码后,代码现在具有明显的单一责任

public class ReallyIntenseCalculation : ICalculation {

    public int calculate(int number) {
        return DoTheDirtyWork(number);
    }

    private int DoTheDirtyWork(int number) {
        // crazy math happens here
    }
}

好的,我们已经删除了ReallyIntenseCalculation记录其内部的能力。我们如何找到一种外部化该功能的方法。输入装饰器模式。

通过创建一个装饰ICalculation的类,我们可以将日志记录添加回混合中,但这样做会损害ReallyIntenseCalculation的私有方法中发生的一些更精细的日志记录。

public class CalculationLoggingDecorator : ICalculation {
    private readonly ICalculation calculation;
    private readonly ILogger log;

    public CalculationLoggingDecorator(ICalculation calculation, ILogger log) {
        this.calculation = calculation;
        this.log = log;
        log.Debug("Instantiated a CalculationLoggingDecorator using " + calculation.ToString());
    }

    public int calculate(int number) {
        log.Debug("Some debug logging.")
        var answer = calculation.calculate(number);
        log.Info(number + " resulted in " + answer);
    }
}

拥有日志装饰器的其他一些可能的优点和缺点是什么?

3 个答案:

答案 0 :(得分:3)

我认为这是值得商榷的。可以说,记录是单一责任的一部分。

那就是说,我认为你正在考虑cross-cutting concerns,这是aspect-oriented programming所处理的问题。实际上,日志代码是AOP的典型示例。您可能需要考虑像aspect#这样的AOP框架。

做这样的事情的好处当然是可分解性,重用和关注点分离。

答案 1 :(得分:3)

我同意杰森的说法,这更像是一个横切关注的问题。

一个函数应该只执行一个函数,因为这样可以使代码更易读,并且更容易测试。例如,如果我的单元测试因日志文件已满而失败,那将会引起混淆,因为我没有在我的方法中测试日志记录。

AOP是这里的最佳选择,对于.NET PostSharp可能是最好的选择。

如果AOP不是一个好的选择,那么你可能想要使用DI,你可以注入一个具有日志记录的类的版本来测试流程,但是如果你要这样做那么你应该确保注入的类只执行日志记录,然后调用没有日志记录的相同函数,因此您将在类中放置一个包装器。

public class ReallyIntenseCalculation : ICalculation {

    public int calculate(int number) {
        return DoTheDirtyWork(number);
    }

    private int DoTheDirtyWork(int number) {
        // crazy math happens here
    }
}

public class CalculationLoggingDecorator : ICalculation {
    ICalculation calculation;
    ILogger log;
    public CalculationLogging() {
        this.calculation = new ReallyIntenseCalculation() ;
        this.log = SomeLogger(...);
        log.Debug("Initialized a CalculationLoggingDecorator using " + calculation.ToString());
    }

    public int calculate(int number) {
        log.Debug("Some debug logging.")
        var answer = calculation.calculate(number);
        log.Info(number + " resulted in " + answer);
    }
}

这与您的装饰器类似,但是当你替换非日志版本的日志版本时,你已经删除了所有多余的代码,并通过测试日志版本来确保正在使用ReallyIntenseCalculation并且这些方法只定义了一次。

这是更多的工作,AOP更可取,但DI可能是另一种选择。

更新:根据评论。

如果您有多个类扩展此接口,那么您可能会爆发类,但为此设计。

AOP将是最好的方法,但这个概念对某些公司来说比DI更难卖。

每个实现最终可能会有两个类,但是仍然会从这些其他类中删除您的日志信息,通过DI注入每个类,因此您可以拥有两个app.config文件,其中一个包含所有日志记录设置课程和制作课程,以简化您的生活。第二类只是记录和设置信息,所以它不是太多额外的工作,我相信,但你已经失去了精细的日志记录。

答案 2 :(得分:0)

你有多少伐木工?我使用Logger单例或Logger工厂方法使计算器的构造函数的实现私有地实例化一个记录器实例(这样日志记录或不记录不是计算器公共API的一部分,而是计算器实现的一部分)。