这不是关于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
的{{1}}更改为将依赖项泄漏给依赖于base
的模块
将base
作为library
的依存关系添加到implementation
,而不会使feature1
泄漏对base
的依存关系
当然,出于这个问题,示例已被简化,但是您了解了如何将其变成具有大量具有4或5个依赖级别的模块的配置地狱。
我们可以创建一个library
中间模块,该模块可以包装base-feature
并为base
提供另一级别的抽象以供使用,而不会泄漏feature1
,但是让我们省略该解决方案问题的范围集中在依赖项的设置上。
一些权衡:
选项1)优点
library
的文件,因为无需重复build.gradle
子句implementation
子句进行一次更改,即可看到更改已传播到所有使用者模块选项1)缺点
选项2)专业人士
选项2)缺点
api
子句的模块,因此更新依赖项变得更加乏味。尽管我认为这是一件好事,因为它可以准确跟踪更改是如何修改项目的,但我知道它可能需要更多时间。现在是问题:
在此多模块方案的编译方面是否有权衡取舍?
模块是否泄漏了“更快”的依赖关系,以便为使用者模块进行编译?
它在构建时间上有实质性的不同吗?
我还缺少其他哪些副作用?
感谢您的时间。
答案 0 :(得分:2)
从Gradle forum线程重新发布。
您所描述的是关于分层体系结构系统的相当普遍的讨论,也称为“严格”层与“松散”层,或“开放”层与“封闭”层。参见Software Architecture Patterns的本章(希望对您也免费)中的一些符号符号,这些符号符号不太可能对您的选择有很大帮助
从我的角度来看,如果模块需要中断分层,则可以对项目结构进行建模,以最直接,最直观的方式展示它。在这种情况下,这意味着添加output
作为[[3, 4, 2, 2, 1],
[4, 5, 2, 2, 1],
[3, 4, 3, 3, 2],
[2, 2, 2, 2, 2],
[1, 1, 2, 2, 2]]
的实现依赖项。是的,它使图表更丑陋,是的,它迫使您在升级时碰几个文件,这就是要点-您的设计有缺陷,现在可以看到。
如果很少有模块需要以相同的方式破坏层封装,我可以考虑添加一个单独的基本模块来公开该功能,其名称为library
。添加新模块是一件大事,不是因为技术上的工作,而是因为我们的大脑一次只能处理这么多的“事物”(块)。我相信,当Gradle“变体”可用时,也是如此,但是我还不能断言,因为我还没有尝试过。
如果feature1
模块的所有客户端都需要访问base-xyz
(即因为您在公共签名中使用了base
的类或异常),则应该公开library
作为library
的API依赖项。缺点是library
成为base
的公共API的一部分,它可能比您想要的更大,并且不受您的控制。公共API是您要负责的事情,您想使其保持小巧,有文档记录并向后兼容。
在这一点上,您可能正在考虑拼图模块(好的),osgi(错误……不要),或者将需要公开的lib部分包装在自己的类中(也许吗?)
仅仅为了打破依赖关系而包装并不是一个好主意。一方面,它增加了您维护(以及希望)文档的代码量。如果您开始在library
层进行小的修改,并且base
是一个众所周知的库,则会引入(增值)不一致性-必须始终警惕其对lib的假设是否仍然成立。最后,薄包装器通常最终会泄漏库设计,因此,即使它们包装了API-在替换/升级lib时仍然会迫使您触摸客户端代码,这时最好直接使用lib。
因此,正如您所看到的,是权衡和可用性。 CPU不在乎模块边界在哪里,并且所有开发人员都不相同-有些可以更好地应对大量简单的事情,有些可以更好地应对少数高度抽象的概念。
在任何好的设计都可行的情况下,不要着迷于最好的设计(就像鲍勃叔叔会做的那样)。为引入顺序而辩解的额外复杂性的数量是一个模糊的数量,您可以自行决定。打给您最好的电话,不要害怕明天更改它:-)