8

如果我想使用 DIP 开发一个假设的模块化 C++ 项目。由于模块化,我选择在一个库中完全实现一项特定功能A。另一个库B(或两个,或三个......)正在使用此功能(例如日志记录机制):

class ILogger
{
    virtual void log(const std::string& s) = 0;
};

我应该把这个接口放在哪里?一些博主似乎建议,因为接口属于它的用户(因为 DIP)你应该把接口放在用户端(或这里)。这也将提高可测试性,因为您不需要将任何实现链接到测试。

这意味着库 A 本身不会编译,因为它缺少接口。这也意味着,如果库 C 也将使用日志记录工具,它还将引入一个接口ILogger,这将破坏ODR?!这可以通过引入一个仅包含接口的额外包层库 D 来解决。但主要问题仍然存在:

接口放在哪里?我阅读了有关 DIP 的原始论文,但我不同意这种解释,即我不应该将接口放入库中。我有一种感觉,这篇论文旨在作为如何思考开发的指南(因为“用户定义的是接口而不是实现者”)。这个对吗?你如何使用依赖倒置原则?

4

1 回答 1

2

软件可以看作是不同层的组合:

  • 一层是实现层(大致就是功能层)

  • 另一个是数据结构相互作用的方式(类级别,主要是 DIP 应该应用的地方)

  • 另一种是组件应该交互的方式(包层)。如果可能的话,我们也想在这里应用某种 DIP。Robert C. Martin 坚持认为这一层主要依赖于业务(无论这意味着什么),因此原则略有不同:Stable-Dependencies Principle 和 Stable-Abstractions Principle(参见 Martin 的 Principle Pattern and Practice)

现在还应该强调软件工程中的原则,即只有在必须解决它们所解决的问题时才应该应用它们。只要您没有问题,就不要使用它们。

在类级别,如果您有充分的理由相信您的日志记录机制将由多个类实现,则应该使用 DIP。如果您认为暂时只有一种日志记录机制,那么使用 DIP 完全没问题,因为没有要解决的问题。

现在应该在包级别做出同样的选择。但是,您的打包选择指南是部署。这里:

class ILogger {
    virtual void log(const std::string& s) = 0;
};
class A : public ILogger {
    …
};
class A2 : public ILogger {
    …
};
  1. 如果您认为(出于商业原因)在没有 A2 的情况下发布 A 是有意义的,那么制作 4 个库:一个用于 ILogger,一个用于用户类 B,一个用于 A,一个用于 A2。
  2. 如果由于某些原因 A 和 A2 应该一起发布,那么只为 ILogger、A 和 A2 制作一个库。如果以后它们应该单独发布,那么打破你的图书馆,但不是现在,因为记住:YAGNI
  3. 如果您对 ILogger 只有一个依赖项,那么只创建一个包含所有内容的库也是有意义的。
  4. 不要发布带有 ILogger 和 B 的库,以及带有 A 的另一个库,因为与解决方案 3 相比,您没有优势,这更复杂,并且可能进一步违反包的另一个原则:非循环依赖原则。

无论如何,这个决定主要取决于业务。还要记住,打包应该是自下而上的:只有当你有很多要组织的类时才创建一个新包。除非你没有这么多的课程,否则不要试图尽早做出决定,因为你几乎肯定会错。

于 2014-09-04T00:03:47.437 回答