Answers:
记录仪是我们所谓的“跨领域关注点”。它们产生于面向方面编程等技术。如果您有一种用属性修饰类或执行一些代码编织的方法,那么这是一种在保持对象和参数列表“纯净”的同时获得日志记录功能的好方法。
您可能希望传递记录器的唯一原因是,如果您想指定不同的记录实现,但是大多数记录框架都具有灵活性,可以让您针对不同的记录目标进行配置。(日志文件,Windows事件管理器等)
由于这些原因,我宁愿使日志记录成为系统的自然组成部分,而不是将记录器传递给每个类以进行记录。因此,我通常要做的是引用适当的日志记录命名空间,并在类中使用logger。
如果您仍想传递记录器,则我希望将其设为参数列表中的最后一个参数(如果可能,使其成为可选参数)。将其作为第一个参数没有多大意义。第一个参数应该是最重要的参数,与类的操作最相关的参数。
您绝对可以花很多时间对这个问题进行过度设计。
对于具有规范记录实现的语言,只需在每个类中直接实例化规范记录器即可。
对于没有规范实现的语言,请尝试查找并遵循日志框架外观框架。slf4j是Java中的不错选择。
我个人宁愿坚持一个具体的日志记录实现,然后将所有内容发送到syslog。所有优秀的日志分析工具都能够将来自多个应用服务器的sysout日志合并到一个综合报告中。
当函数签名包含一个或两个依赖服务以及一些“真实”参数时,我将依赖关系放在最后:
int calculateFooBarSum(int foo, int bar, IntegerSummationService svc)
由于我的系统往往只有五个或更少的此类服务,因此我始终确保在所有功能签名中以相同的顺序包含这些服务。字母顺序和其他顺序一样好。(此外:保持这种方法学方法进行互斥处理也将减少产生死锁的机会。)
如果您发现自己在整个应用程序中注入了十几个依赖关系,则可能需要将系统拆分为单独的子系统(敢于说微服务吗?)。
记录器有点特殊情况,因为它们实际上必须在任何地方都可用。
如果您决定要将记录器传递到每个类的构造函数中,那么您绝对应该为操作方法设置一致的约定(例如,始终第一个参数,始终通过引用传递,构造函数初始化列表始终会开始使用m_logger(theLogger)等)。有一天,整个代码库中将要使用的所有内容都将从一致性中受益。
或者,您可以让每个类实例化自己的记录器对象,而不需要传递任何内容。记录器可能需要“凭魔力”知道一些事情才能起作用,但是在类定义中对文件路径进行硬编码可能是与正确地将其正确传递给数百个不同的类相比,它具有更大的可维护性和乏味性,并且可以说,与使用全局变量绕过所述乏味相比,其邪恶性要小得多。(公认的是,记录器是全局变量的极少数合法用例之一)
我同意那些建议记录器应该被静态访问而不是传递给类的建议。但是如果你想要将它传递一个强有力的理由(也许是不同的情况下,要登录到不同的位置或东西),那么我会建议你不要使用构造函数传递,而是进行单独的调用来做到这一点,例如Class* C = new C(); C->SetLogger(logger);
相当比Class* C = new C(logger);
选择此方法的原因是,记录器本质上不是类的一部分,而是用于其他目的的注入功能。将其放在构造函数列表中会使它成为类的要求,并暗示它是类的实际逻辑状态的一部分。例如,可以合理地预期(对于大多数类,虽然不是全部),但如果X != Y
这样,C(X) != C(Y)
但是如果您也比较同一类的实例,则不太可能测试记录器不平等。
值得一提的是,在这里我还没有看到其他答案,这是因为通过使记录器通过属性或静态方式注入,使得难以对类进行单元测试。例如,如果使记录器通过属性注入,则现在每次测试使用记录器的方法时都必须注入该记录器。这意味着您最好将其设置为构造函数依赖项,因为该类确实需要它。
静态也适用于同一问题;如果记录器不起作用,则整个类都会失败(如果您的班级使用记录器),即使记录器不一定是类责任的“一部分”,尽管它并没有像注入属性那样糟糕,因为至少知道记录器在某种意义上总是“存在”。
只是需要一些思考,特别是如果您使用TDD。我认为,记录器实际上不应该属于类的可测试部分(当您测试该类时,您也不应同时测试您的日志记录)。