我是一个从事相当大项目的团队的新手,有很多组件和依赖项。对于每个组件,都有一个interfaces
用于放置该组件的公开接口的包。这是一个好习惯吗?
我通常的做法是接口和实现放在同一个包中。
放置接口和实现是常见的地方,似乎不是问题。
以 Java API 为例——大多数类的接口和它们的实现都包含在同一个包中。
以java.util
包装为例:
它包含诸如Set
, Map
,之类的接口List
,同时也具有诸如HashSet
,HashMap
和之类的实现ArrayList
。
此外,Javadocs 被设计为在这些条件下运行良好,因为它在显示包的内容时将文档分为接口和类视图。
只为接口提供包实际上可能有点过分,除非有大量的接口。但是仅仅为了这样做而将接口分离到它们自己的包中听起来是不好的做法。
如果需要将接口的名称与实现区分开来,可以使用命名约定来使接口更易于识别:
在接口名称前加上I
. 这种方法适用于 .NET 框架中的接口。很容易看出这IList
是一个列表的接口。
使用 -able
后缀。这种方法经常出现在 Java API 中,例如Comparable
, Iterable
,Serializable
等等。
对于任何语言,将它们放在同一个包中都可以。重要的是暴露给外界的东西,以及它从外面看的样子。没有人会知道或关心实现是否在同一个包中。
让我们看看这个特殊的例子。
如果您将所有公共内容放在一个包中,而将私有内容放在另一个未公开的包中,则库的客户端会看到一个包。如果您将私有事物与公开暴露的事物一起移动到包中,但不从包中公开它们,客户端会看到完全相同的事物。
因此,这有一种没有充分理由的规则的味道:它是根据公开可见的事物做出决定,而该决定对公开可见的事物没有任何影响。
也就是说,如果在任何特定情况下,将接口和实现拆分到单独的包中似乎是个好主意,那就继续做吧。我想到的这样做的原因是软件包很大,或者您可能想要链接的替代实现而不是标准实现。
在许多框架中,例如 OSGi,您几乎必须这样做。我认为这促进了更松散的耦合,在包而不是 jar 级别。
《实用软件工程:案例研究方法》一书提倡将接口放在单独的项目/包中。
书中谈到的PCMEF+架构有以下几个原则:
原则 #3 和 #7 的描述解释了为什么这是一个好主意:
邻居通信原则要求一个包只能与它的邻居包直接通信。该原则确保系统不会分解为不可压缩的相互通信对象网络。为了强制执行这一原则,非相邻对象之间的消息传递使用委托(第 9.1.5.1 节)。在更复杂的场景中,熟人包(第 9.1.8.2 节)可用于对接口进行分组,以协助参与远程包的协作。
熟人包原则是邻居通信原则的结果。熟人包由对象在方法调用的参数中传递的接口组成,而不是具体对象。这些接口可以在任何 PCMEF 包中实现。这有效地允许非相邻包之间的通信,同时将依赖管理集中到单个熟人包。第 9.1.8.2 节解释了熟人包的必要性,接下来在 PCMEF 上下文中再次讨论。
将接口放在不同包中的一个论点是,创建可以分发给您的产品或服务的消费者的“api”jar 更容易。完全可以将接口和实现一起使用,但如果它们位于不同的包中,则编写脚本会更简单。
是的,这是一个非常好的做法,因为它允许您在不发布特定实现的情况下发布接口。也就是说,如果您不需要发布外部接口,那么将接口定义与实现放在同一个包中是没有问题的。
我们在我工作的地方这样做(即:将接口放在一个包中,将实现放在另一个包中),我们从中获得的主要优势是我们可以轻松地在实现之间进行交换。
我没有太多 Java 经验,但我喜欢在 C#/.NET 中将其作为一种良好实践,因为它允许将来扩展,其中具有实现接口的具体类的程序集可能不会一直分布到客户端,因为它们由中间人工厂代理或在 Web 服务场景中通过网络进行代理。
我通常将接口与实现放在一起,但是我可以理解为什么您可能希望将它们分开。比如说,有人想根据你的接口重新实现类,他们需要一个 jar/lib/etc 来实现你的实现,而不仅仅是接口。将它们分开时,您只需说“这是我对该接口的实现”并完成它。就像我说的,不是我做什么,但我可以理解为什么有些人可能想要。
我正在一个项目上这样做,由于以下原因,它对我来说效果很好:
单独打包的接口和实现用于与各种类型的 Map 或 Set 不同的用例。没有理由只为树创建一个包(java.util.tree.Map、java.util.tree.Set)。它只是一个标准的数据结构,所以将它与其他数据结构放在一起。但是,如果您正在处理一个具有非常简单的调试界面和漂亮的生产界面的益智游戏,作为其前端的一部分,您可能有一个 com.your.app.skin.debug 和一个 com.your.app .皮肤.漂亮。我不会将它们放在同一个包中,因为它们会做不同的事情,而且我知道如果它们在同一个包中,我会使用一些 SmurfNamingConvention(DebugAttackSurface、DebugDefenceSurface、PrettyAttackSurface 等)为两者创建一些非正式的命名空间。
对于查找位于单独包中的相关接口和实现的问题,我的解决方法是为我的包采用命名配置。例如,我可以在 com.your.app.skin.framework 中拥有我的所有接口,并且知道包树同一级别上的其他包是实现。缺点是这是一个非常规的约定。老实说,我会在 6 个月内看到这个大会有多好 :)
我不虔诚地使用这种技术。有些接口仅在特定实现中才有意义。我不会将它们粘贴在框架包中。有些包看起来不像我要创建 40 个不同的实现类,所以我不打扰。
我的应用程序使用 guice 并且有很多接口。
程序设计的问题通常涉及利弊,没有一刀切的答案。比如为什么你会在一个只有 200 行的小程序中使用这种技术?考虑到我的其他架构选择,对于我的具体问题,这对我来说很有意义。:)
我更喜欢它们在同一个包中。专门为接口创建一个包感觉毫无意义。这对于只喜欢他们的接口前缀和后缀(例如 Java 的“I”和“Impl”)的团队来说尤其多余。
如果需要将一组接口作为公共 API 发布,将它们保存在一个完全独立的项目中并创建项目依赖项会更有意义。但这一切都归结为我认为情况的偏好和便利。
将它们放在反映您的项目的包中。如果接口和实现是同一个项目的一部分,将它们放在一起很好也很常见,但是如果您正在编写 API,那么其他人可能会选择与他们的项目相关的包名称。
一般来说,如果它是针对同一个项目的,我认为将接口与它们的实现放在一个单独的包中没有任何好处。如果它变得杂乱无章,则可能存在其他包命名问题,而与接口排列无关。
我认为重要的是要注意,对于 OSGi 框架,最好将它们放在不同的包中,这样您就可以轻松地导出整个包,同时隐藏实现包。