这不是关于api
和implementation
之间差异的常见问题,并且从构建多模块项目的角度来看,希望会更加先进和有趣。
假设我在应用程序中有以下模块
library
base
feature1
feature2
app
现在模块之间的关系是:
base
包裹library
feature1
和feature2
在base
上使用(取决于)
app
汇集了feature1
和feature2
这个多模块结构中的所有东西都应该能够使用Gradle的implementation
依赖项工作,并且不需要在任何地方使用api
子句。
现在,让我们说feature1
需要访问base
中包含的library
的实现细节。
为了使library
可用于feature1
,据我所知,我们有两个选择:
implementation
中为api
更改base
以将依赖性泄露给依赖于base
的模块library
作为implementation
依赖feature1
没有base
泄漏对library
的依赖当然,为了解决这个问题,这个例子已经被简化了,但是你明白了这可能会成为一个具有4或5级依赖关系的大量模块的配置地狱。
我们可以创建一个base-feature
中间模块,它可以包装base
并为feature1
提供另一层抽象而不会泄漏library
,但是让我们将这个解决方案放在这个问题的范围之外,专注于依赖项的设置。
我在上述选项中检测到的一些权衡:
选项1)到
build.gradle
的文件,因为不需要重复implementation
条款api
子句进行单一更改,并查看传播到所有使用者模块的更改选项1)缺点
2)走向
选项2)缺点
implementation
子句的模块。即使我认为这是一件好事,因为它完全跟踪变更如何修改项目,我看到它可能需要更多时间。现在的问题是:
谢谢你的时间。
从Gradle forum线程重新发布。
您所描述的是关于分层架构系统的相当常见的讨论,也称为“严格”与“松散”分层,或“开放”与“封闭”层。从Software Architecture Patterns看到这个(希望对你来说也是免费的)章节中的一些符号学不太可能对你的选择有所帮助
从我的角度来看,如果一个模块需要打破分层,我会对项目结构进行建模,以最直接和最明显的方式公开它。在这种情况下,它意味着添加library
作为feature1
的实现依赖。是的,这使得图表更加丑陋,是的,它迫使您在升级时触摸更多文件,这就是重点 - 您的设计存在缺陷,现在可见。
如果很少有模块需要以相同的方式打破层封装,我可以考虑添加一个单独的基本模块来公开该功能,其名称如base-xyz
。添加新模块是一件大事,不是因为技术工作,而是因为我们的大脑一次只能处理这么多“事物”(分块)。我相信当Gradle“变种”可用时,它们会同样适用,但我还没有声称,因为我没有尝试过它们。
如果base
模块的所有客户端都需要访问library
(即因为您在公共签名中使用来自library
的类或异常),那么您应该将library
作为base
的API依赖项公开。其缺点是library
成为base
的公共API的一部分,它可能比你想要的更大,而且不在你的控制之下。公共API是您负责的事情,并且您希望将其保持小,记录和向后兼容。
此时你可能正在考虑拼图模块(好),osgi(错误...不要),或者包装你需要在自己的类中暴露的lib部分(也许?)
仅仅为了破坏依赖性而包装并不总是一个好主意。例如,它增加了您维护和(希望)文档的代码量。如果你开始在base
层进行小的改编,并且library
是一个众所周知的库,你引入(增值)不一致 - 人们需要始终警惕他们对lib的假设是否仍然成立。最后,瘦包装器通常会泄漏库设计,所以即使它们包装了API - 当你更换/升级lib时仍然会强迫你触摸客户端代码,此时你可能最好直接使用lib。
因此,正如您所看到的,是关于权衡和可用性。 CPU并不关心模块边界所在的位置,并且所有开发人员都是不同的 - 有些人会更好地处理大量简单的事情,有些人会更好地处理少量高度抽象的概念。
当任何好的设计都能奏效时,不要痴迷于最好的(就像在鲍勃叔叔那里做的那样)。为了引入顺序而合理的额外复杂性的数量是模糊量,并且是您负责决定的事情。让你最好的电话,不要害怕明天改变它:-)