使用跨模块的依赖倒置原则正确设计?

Correct design using dependency inversion principle across modules?

我了解在单个模块内工作时的依赖倒置,但我也想在有跨模块依赖时应用它。在下图中,我有一个现有应用程序,我需要为参考数据服务实现一些新要求。我想我会创建一个新的 jar(将来可能是一个独立的服务)。第一张图显示了我过去处理此类事情的正常方式。 referencedataservices jar 有一个应用程序将用来调用它的接口。

第二个图显示了我尝试使用 DIP,应用程序现在拥有它的抽象,因此它不会因为参考数据服务的变化而改变。但这似乎是一个错误的设计,因为它会产生循环依赖。 MyApp依赖referencedataservices jar,referencedataservices jar依赖MyApp。

所以第三个图通过创建一个额外的抽象层回到更正常的依赖关系。我对吗?或者这真的不是 DIP 的目的吗?有兴趣了解其他方法或建议。

在您介绍的第二种方法中,如果您将 RefDataSvc 抽象移动到单独的包,您将打破循环,并且 referencedataservices 包仅使用具有 RefDataSvc 抽象的包。

除了应用程序的 Composition Root in MyApp package should depend also on RefDataSvc. In Composition Root 之外的其他代码,您还应该编写应用程序所需的所有依赖项。

第二个示例通过将实现与其抽象分离而走在了正确的轨道上。为了实现模块化,具体的 class 不应与其抽象接口位于同一个包(模块)中。

第二个例子的错误在于客户端拥有抽象,而服务拥有实现。这两个角色必须互换:服务拥有接口;客户自己的实现。通过这种方式,服务会呈现一个契约 (API) 供客户端执行。该服务保证与任何遵守其 API 的客户端进行交互。在依赖倒置方面,客户端向服务中注入依赖。

Kirk K. 在 Java 中是模块化方面的权威。他有一个博客,最终变成了一个 book on the subject. His blog seems to be missing at the moment, but I was able to find it in the Wayback Machine. I think you would be particularly interested in the four-part series titled Applied Modularity. In terms of other approaches or alternatives to DIP, take a look at Fun With Modules,涵盖了其中三个。