【问题标题】:Which effects does the Dependency Inversion Principle have to a project structure?依赖倒置原则对项目结构有哪些影响?
【发布时间】:2014-10-20 13:17:02
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 将接口放在单独的库中怎么样?然后,您可以从 A 和 B 中包含该库。最后,建议用于 DIP ...
  • 对于大多数用例来说,这可能被夸大了。对我来说最有问题的情况是:如果您将接口与用户一起打包,并将实现打包在另一个包中,那么它将从带有实现的包添加到带有用户和接口的包的构建依赖项。恕我直言,仅仅为了获得一点可测试性优势,这是不合理的。

标签: c++ deployment dependencies modularity dependency-inversion


【解决方案1】:

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

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

  • 另一个是数据结构交互的方式(类级别,主要是应用 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 相比,您没有任何优势,这更复杂,并且还可能违反包的另一个原则:非循环依赖原则。李>

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

【讨论】:

  • 谢谢你的答案。很明显,我必须添加一个额外的层,如果在不同的库中有 2 个接口的实现,但 DIP 似乎坚持,你需要抽象,如果有多个用途,因为接口属于用户?!您在枚举中的第 4 个条目似乎违反了我链接的网站的 DIP 解释? R.C. Martin 是否在任何地方都坚持与用户打包界面?
  • Martin 没有告诉将接口与其客户端打包。如果您有多个客户,您将如何打包?所以在包层面,界面绝对不属于用户。即使在班级层面,“属于客户”实际上意味着“客户依赖它”。如果您真的不想依赖 ILogger 的另一个客户端可能导致的更改,请创建另一个接口,例如使用 Adapter 设计模式的 ILoggerForB。由于ILoggerForB是只为B创建的,它唯一的客户端是B,所以可以和B一起打包。
  • 感谢您澄清这一点。
猜你喜欢
  • 1970-01-01
  • 2017-09-09
  • 1970-01-01
  • 2013-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-27
相关资源
最近更新 更多