接口应该放在单独的包中吗? [关闭]
Posted
技术标签:
【中文标题】接口应该放在单独的包中吗? [关闭]【英文标题】:Should interfaces be placed in a separate package? [closed] 【发布时间】:2010-11-03 13:10:06 【问题描述】:我是一个团队的新手,该团队正在处理一个相当大的项目,其中包含许多组件和依赖项。对于每个组件,都有一个 interfaces
包,用于放置该组件的公开接口。这是一个好习惯吗?
我通常的做法是接口和实现放在同一个包中。
【问题讨论】:
为什么要标记为 [与语言无关的]? 另一种情况,如果模块要通过一些 rpc 而不是直接调用进行通信,那么这可能是首选的情况 - 然后接口可以方便地为客户端生成代理?? 【参考方案1】:Practical Software Engineering: A Case-Study Approach 这本书提倡将接口放在单独的项目/包中。
本书所讲的PCMEF+架构有以下原则:
-
向下依赖原则 (DDP)
向上通知原则 (UNP)
邻居通信原则 (NCP)
显式关联原则 (EAP)
循环消除原则 (CEP)
类命名原则 (CNP)
熟人包原理(APP)
原则 #3 和 #7 的描述解释了为什么这是一个好主意:
邻居通信原则要求一个包只能 直接与其邻居包通信。这一原则确保 系统不会分解为不可压缩的网络 相互交流的对象。为了执行这个原则,消息传递 非相邻对象之间使用委托(第 9.1.5.1 节)。在 更复杂的场景,熟人包(9.1.8.2节)可以 用于对界面进行分组以协助参与的协作 遥远的包裹。
熟人包原则是邻居的结果 沟通原则。熟人包包括 对象传递的接口,而不是具体对象,在 方法调用的参数。这些接口可以在任何 PCMEF 包。这有效地允许之间的通信 非相邻包,同时将依赖管理集中到一个 单身熟人包。熟人包的需要是 在第 9.1.8.2 节中进行了解释,接下来在 PCMEF 中再次讨论 上下文。
查看此链接:http://comp.mq.edu.au/books/pse/about_book/Ch9.pdf
【讨论】:
【参考方案2】:对于任何语言,将它们放在同一个包中都可以。重要的是暴露给外界的东西,以及它从外面看起来的样子。没有人会知道或关心实现是否在同一个包中。
让我们看看这个特殊的例子。
如果您将所有公共内容放在一个包中,而将私有内容放在另一个未公开的包中,则库的客户端会看到一个包。如果您将私有事物与公开暴露的事物一起移动到包中,但不从包中公开它们,那么客户端看到的内容完全相同。
因此,这有一种没有充分理由的规则的味道:它是根据公开可见的内容做出决定,而该决定对公开可见的内容没有任何影响。
也就是说,如果在任何特定情况下,将接口和实现拆分到单独的包中似乎是个好主意,请继续这样做。我想到的这样做的原因是软件包很大,或者您可能想要链接一个替代实现而不是标准实现。
【讨论】:
鉴于问题上的“Java”标签 - Java 中的“publicly 公开包”是什么意思?还是您的答案不是特定于 Java 的?我认为只有包成员(例如类、接口及其成员),而不是包本身,具有访问控制说明符,如“public”、“private”等。(这将进一步讨论@问题***.com/questions/4388715/…) 我只是澄清了措辞;我所说的“公开暴露的包”实际上是指“带有公开暴露的东西的包。” 当然所有的包都应该有公共的东西;如果没有,你怎么能使用这个包? 在这种特殊情况下,有些包没有公开,但被公开的包使用。所以前者不能直接使用,只能通过后者间接使用。 但同样的问题也适用。公开的包如何使用没有公开的包?我知道在 Java 中没有办法强制“前者不能直接使用,而只能通过后者间接使用”。如果你的实现包只有私有成员,你的接口包和客户端一样不能访问它。 & 对于其他访问范围类似。 Java 中没有friend
(C++) 或 internal
(C#) 等价物。【参考方案3】:
我正在一个项目中这样做,由于以下原因,它对我来说效果很好:
单独打包的接口和实现用于与各种类型的 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 行的小程序中使用这种技术?考虑到我的其他架构选择,对于我的具体问题,这对我来说很有意义。 :)
【讨论】:
【参考方案4】:我认为需要注意的是,对于 OSGi 框架,最好将它们放在不同的包中,这样您就可以轻松地导出整个包,同时隐藏实现包。
【讨论】:
【参考方案5】:将接口放在与实现分开的包中有很多很好的理由。例如,您可能希望使用支持Dependency Injection 的容器在运行时连接必要的实现,在这种情况下,只需要在构建时提供接口,并且可以在运行时提供实现。或者,您可能希望在运行时提供多个实现(例如使用 mock 实现进行测试)或特定实现的多个版本(例如用于 A/B 测试等)。对于这类用例,将接口和实现分开打包会更方便。
【讨论】:
您无需将实现放在不同的包中即可完成您列出的任何一项操作 这很公平,但我仍然认为“在构建时与运行时存在”的需求差异是单独打包的一个令人信服的案例。我更新了文本以更清楚地反映这一点。【参考方案6】:我通常将接口与实现放在一起,但是我可以理解为什么您可能希望将它们分开。例如,有人想根据你的接口重新实现类,他们需要一个 jar/lib/etc 来实现你的实现,而不仅仅是接口。将它们分开时,您只需说“这是我对该接口的实现”并完成它。就像我说的,不是我做什么,但我可以理解为什么有些人可能想要。
【讨论】:
【参考方案7】:我更喜欢它们在同一个包中。专门为接口创建一个包感觉毫无意义。这对于只喜欢他们的接口前缀和后缀(例如 Java 的“I”和“Impl”)的团队来说尤其多余。
如果需要将一组接口发布为公共 API,将它们保存在一个完全独立的项目中并创建项目依赖项会更有意义。但这一切都归结为我认为情况的偏好和便利。
【讨论】:
【参考方案8】:在许多框架中,例如 OSGi,您几乎必须这样做。我认为这会在包而不是 jar 级别促进更松散的耦合。
【讨论】:
【参考方案9】:将它们放在反映您的项目的包中。如果接口和实现是同一个项目的一部分,那么将它们放在一起很好也很常见,但是如果您正在编写 API,那么其他人可能会选择与他们的项目相关的包名称。
一般来说,如果是针对同一个项目,我认为将接口与它们的实现放在一个单独的包中没有任何好处。如果它变得杂乱无章,则可能存在其他包命名问题,而与接口排列无关。
【讨论】:
【参考方案10】:同时放置接口和实现是常见的地方,似乎没有问题。
以 Java API 为例——大多数类的接口及其实现都包含在同一个包中。
以java.util
包为例:
它包含Set
、Map
、List
等接口,同时也有HashSet
、HashMap
和ArrayList
等实现。
此外,Javadocs 旨在在这些条件下正常工作,因为它在显示包的内容时将文档分为 Interfaces 和 Classes 视图。
只为接口提供包实际上可能有点过分,除非有大量的接口。但是仅仅为了这样做而将接口分离到自己的包中听起来是不好的做法。
如果需要将接口的名称与实现区分开来,可以有一个命名约定以使接口更易于识别:
在接口名称前加上 I
前缀。 这种方法适用于 .NET 框架中的接口。很容易看出IList
是一个列表接口。
使用 -able
后缀。 这种方法经常出现在 Java API 中,例如 Comparable
、Iterable
和 Serializable
等等。
【讨论】:
+1 虽然我不推荐 I* 命名约定,除非你在 .NETverse 中,即使那样也只是为了保持一致性 我在 java 代码中使用了这个约定,我发现它很有用.... YMMV 我知道这有点老了,但我现在发现建议是清楚地命名接口,并将实现命名为 strager。例如Interface = Store Implementation = StoreImpl 主要是为了强调接口应该用在实现之上。 我认为这种方法即使看起来正确,也会带来架构问题。例如,没有办法定义只使用接口的库,然后在一个公共库中实现它们,即基础设施层。 我同意关于不对接口使用“I”前缀的评论,就像我不建议在实现中使用“impl”后缀一样,这是一篇很好的文章,反映了这些情况:octoperf.com/blog/2016/10/27/impl-classes-are-evil【参考方案11】:将接口放在不同包中的一个论点是,更容易创建可以分发给您的产品或服务的消费者的“api”jar。将接口和实现一起使用是完全可能的,但如果它们位于不同的包中,则编写脚本会更简单。
【讨论】:
您可以将 API 项目分开(作为一个单独的可分发),但这仍然不会阻止接口和实现在同一个包中。有点像您在与测试类相同的包中进行单元测试,但仍在单独的构建工件中。 jar 和包在 Java 中几乎是正交的。【参考方案12】:我没有太多的 Java 经验,但我喜欢在 C#/.NET 中将其作为一个很好的实践,因为它允许将来扩展具有实现接口的具体类的程序集可能不会一直分布到客户端,因为它们由中间工厂代理或在 Web 服务场景中通过网络进行代理。
【讨论】:
【参考方案13】:我们在我工作的地方这样做(即:将接口放在一个包中,将实现放在另一个包中),我们从中获得的主要优势是我们可以轻松地在实现之间进行交换。
【讨论】:
它如何帮助您切换实现? 看Cameron Pope的回答,这正是我们在这里做的原因【参考方案14】:是的,这是一个非常好的做法,因为它允许您在不发布特定实现的情况下发布接口。也就是说,如果您不需要发布外部接口,那么将接口定义与实现放在同一个包中是没有问题的。
【讨论】:
也许如果您正在为 JCP 定义接口或其他东西这很有意义,但如果您正在为您的项目生成这些接口的多个具体实现,那么单独打包接口似乎是不必要的负担. 这不是什么负担,无论如何... -1 是的。它使查找实现和分离属于一起的事物变得不必要地困难。 不发布实现是完全不同的事情,可以通过将它们设为包私有来完成 - 并且不能通过将它们放在单独的包中来完成。没有非公共包这样的东西。 此外,这听起来像是让所有接口都不必要地公开。以上是关于接口应该放在单独的包中吗? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章
当你在 MySQL 或 PostgreSQL 中有一个 TEXT 字段时,你应该把它放在一个单独的表中吗?