获取对象参数或使用成员对象更好吗?

时间:2010-10-29 11:23:14

标签: c++ oop

我有一个课,我可以这样写:

class FileNameLoader
{
     public:
         virtual bool LoadFileNames(PluginLoader&) = 0;
         virtual ~FileNameLoader(){}
};

或者这个:

class FileNameLoader
{
     public:
         virtual bool LoadFileNames(PluginLoader&, Logger&) = 0;
         virtual ~FileNameLoader(){}
};

第一个假定Logger&的实施中有成员FileNameLoader。第二个没有。但是,我有一些类有很多内部使用Logger的方法。所以第二种方法会让我在这种情况下编写更多代码。 Logger目前是单身人士。我的猜测是它会保持这种状态。这两者中更“美丽”的是什么?为什么?通常的做法是什么?

编辑:    如果此类未命名为Logger,该怎么办? :)。我也有Builder。那怎么样?

6 个答案:

答案 0 :(得分:7)

我没有看到两个方法有多于一个的额外优势(甚至考虑单元测试!),实际上有两个,你必须确保在你调用特定方法的任何地方,一个Logger可以传入 - 而且可能会让事情变得复杂......

使用记录器构建对象后,您是否真的看到需要更改它?如果没有,为什么还要接受方法二?

答案 1 :(得分:5)

我更喜欢第二种方法,因为它允许更强大的黑盒测试。它还使函数的接口更清晰(事实上它使用了这样的Logger对象)。

答案 2 :(得分:3)

首先要确保用户在任何一种情况下都提供了Logger依赖项。据推测,在第一种情况下,FileNameLoader的构造函数采用Logger&参数?

在任何情况下,我都不会将Logger变为单身人士。从来没有,没有,没有办法,也没有办法。它既可以是注入依赖项,也可以是Log自由函数,或者如果绝对必要,可以使用对std::ostream对象的全局引用作为通用默认记录器。 Singleton Logger类是一种为测试创建障碍的方法,绝对没有实际好处。那么如果某个程序确实创建了两个Logger对象呢?为什么这甚至是坏事,更不用说为自己制造麻烦以防止?在任何复杂的日志记录系统中,我发现自己要做的第一件事就是创建一个PrefixLogger,它实现Logger接口,但在所有消息的开头打印一个指定的字符串,以显示一些上下文。 Singleton与这种动态灵活性不相容。

然后,第二件事是询问用户是否想要拥有一个FileNameLoader,并多次调用LoadFileNames,第一次使用一个记录器,第二次使用另一个记录器。

如果是这样,那么你肯定想要一个函数调用的Logger参数,因为更改当前Logger的访问器是(a)不是一个很好的API,并且(b)无论如何都不可能使用引用成员:你有更改为指针。您可以将logger参数设置为默认值为0的指针,但0表示“使用成员变量”。这将允许用户初始设置代码知道并关心日志记录的用途,但随后该代码将FileNameLoader对象移交给将调用LoadFileNames但不知道或不关心日志记录的其他代码。

如果没有,那么Logger依赖项对于类的每个实例都是不变的,并且使用成员变量就可以了。我总是担心引用成员变量,但出于与此选择无关的原因。

[编辑关于构建器:我认为你可以在我的答案中进行相当多的搜索和替换,它仍然有效。关键的区别在于“此FileNameLoader对象使用的构建器”对于给定对象是否是不变的,或者“在调用中使用的构建器”是否是LoadFileNames的调用者需要在每次调用的基础上配置的。

我可能会稍微坚持认为Builder不应该是Singleton。略。可能。]

答案 3 :(得分:1)

总的来说,我认为较少的论点等于更好的功能。通常,函数具有的参数越多,函数趋于变得越“共同” - 这反过来会导致大量复杂的函数尝试执行所有操作。

假设Logger接口用于跟踪,在这种情况下,我怀疑FileNameLoader类的用户是否真的想要提供应该使用的特定日志记录实例。

您也可以将Demeter法应用为反对在函数调用上提供日志记录实例的参数。

当然会有特定的时间,这是不合适的。一般例子可能是:

  • 表现(只应在确定具体的表现问题后才能完成)。
  • 通过模拟对象帮助测试(在这种情况下,我认为构造函数是一个更合适的位置,对于记录剩余单例可能是更好的选择......)

答案 4 :(得分:1)

我会坚持使用第一种方法并将Logger用作单身人士。不同的接收器和识别数据记录的位置是一个不同的问题。识别接收器可以根据需要简单或复杂。例如(假设Singleton<>是代码中单例的基类):

class Logger : public Singleton<Logger>
{
public:
    void Log(const std::string& _sink, const std::string& _data);
};

你的班级:

class FileNameLoader
{
 public:
     virtual bool LoadFileNames(PluginLoader& _pluginLoader)
     {
         Logger.getSingleton().Log("FileNameLoader", "loading xyz");
     };

     virtual ~FileNameLoader(){}
};

您可以拥有一个固有的复杂日志管理器,它具有不同的接收器,不同的日志级别不同的输出。日志管理器上的Log()方法应该支持如上所述的简单日志记录,然后您可以允许更复杂的示例。例如,出于调试目的,您可以为不同的接收器定义不同的输出以及组合日志。

答案 5 :(得分:0)

我最喜欢的日志记录方法是在我的类中有一个Logger类型的成员(不是引用或指针,而是实际对象)。
根据日志记录基础结构,可以在每个类的基础上决定输出应该去哪里或使用哪个前缀。

这比你的第二种方法更有优势,你不能(意外地)创建一种情况,即在日志文件中不能轻易地识别同一类的成员。