接口应该放在单独的包装中吗?[关闭]


79

我是一个刚开始从事大型项目的团队的新手,该项目具有许多组件和依赖项。对于每个组件,都有一个interfaces包装,用于放置该组件的暴露接口。这是一个好习惯吗?

我通常的做法一直是将接口和实现放在同一个程序包中。


4
为什么将其标记为[与语言无关]?
finnw 2011年

如果要通过某个rpc而不是直接调用该模块进行通信,则可能更希望采用这种方法,这时接口很方便为客户端生成代理?
redzedi

Answers:


100

放置接口和实现都是司空见惯的事,这似乎不是问题。

以Java API为例,大多数类在同一包中都包含了接口及其实现。

java.util包装为例:

它包含的接口,如SetMapList,同时还具有实现,如HashSetHashMapArrayList

此外,Javadocs旨在在这些条件下很好地工作,因为当显示包的内容时,它将文档分为接口视图。

除非有大量的接口,否则仅具有用于接口的软件包实际上可能有点多余。但是仅仅为了这样做,将接口分成自己的程序包听起来是一种不好的做法。

如果需要将接口名称与实现区别开来,则可以使用一种命名约定来使接口更易于识别:

  • 在接口名称前添加I.NET框架中的接口采用了这种方法。可以很容易地说出这IList是列表的接口。

  • 使用-able后缀。这种方法是Java API中经常看到,比如ComparableIterableSerializable仅举几例。


43
+1,尽管除非您使用.NETverse,否则我不建议使用I *命名约定,即使那样也只是为了保持一致性
CurtainDog

7
我在Java代码中使用了此约定,并且发现它很有用.... YMMV
hhafez

5
我知道这有点陈旧,但是现在发现建议我给接口起一个清晰的名称,并将实现的名称更准确一些。例如,接口=存储实现= StoreImpl主要突出显示应该在实现上使用该接口。
2015年

1
我认为这种方法即使看起来正确,也会带来体系结构问题。例如,无法定义仅使用接口的库,然后在公共库(即基础结构层)中实现它们。
卡·马塞拉

1
我同意关于不为接口使用“ I”前缀的评论,就像我不建议为实现使用“ impl”后缀一样,这是一篇很好的文章,反映了这些情况:octoperf.com/blog/2016 /
10/27

19

对于任何语言,将它们放在同一程序包中都可以。重要的是暴露给外界的东西,以及它从外部看的样子。没有人会知道或不在乎实现是否在同一程序包中。

让我们看一下这个特定实例。

如果您将所有公共物品放在一个包中,而私人物品放在另一个未公开的包中,则库的客户端将看到一个包。如果您将私有物品与公开暴露的物品一起移到包装中,但不从包装内部暴露它们,则客户会看到完全相同的事物。

因此,这没有规则的味道,它没有充分的理由:它是基于公开可见的内容来做出决定,而该决定不会对公众可见内容产生任何影响。

就是说,如果在任何特定情况下,将接口和实现拆分为单独的包似乎是个好主意,请继续进行。想到这样做的原因是该程序包很大,或者您可能想链接一个替代的实现而不是标准的实现。


给定问题的“ Java”标记-Java中“公开包”的含义是什么?还是您的答案不是特定于Java的?我以为只有包成员(例如类,接口及其成员)而不是包本身具有访问控制说明符,例如“ public”,“ private”等。(这在@ question stackoverflow.com/questions/4388715/中有进一步讨论。)
bacar 2012年

我只是澄清了措辞;我所说的“公开包装”是指“带有公开包装的东西的包装”。
cjs 2012年

当然,所有的包装都应该有公共物品。如果没有,如何使用该包装?
bacar 2012年

在这种情况下,有些程序包没有公开,但是被那些使事情公开的程序包所使用。因此,前者不能直接使用,而只能通过后者间接使用。
cjs 2014年

1
但是,同样的问题也适用。如何将公开的程序包使用不公开的程序包?我不知道在Java中强制执行“前者不能直接使用,而只能通过后者间接使用”。如果您的实现包只有私有成员,那么接口包和客户端一样无法访问它。和类似的其他访问范围。Java中没有等效的friend(C ++)或internal(C#)。
bacar 2014年

10

在许多框架中,例如OSGi,您几乎都必须这样做。我认为这促进了在包装而非罐子级别的松耦合。


7

将接口放在不同包中的一个论据是,创建可以分发给产品或服务使用者的“ api”罐子更容易。完全可以通过接口和实现一起执行此操作,但是如果它们位于不同的程序包中,则编写脚本更容易。


7
您可以将API项目分开(作为单独的可分发项目),并且仍然不会停止接口和实现位于同一包中。有点像您将单元测试与测试的类放在同一个程序包中,但仍在单独的构建工件中。Jar和包在Java中几乎是正交的。
乔治

7

实用软件工程:案例研究方法》一书提倡将接口放在单独的项目/程序包中。

本书所讨论的PCMEF +体系结构具有以下原则:

  1. 向下依赖原则(DDP)
  2. 向上通知原则(UNP)
  3. 邻居通讯原理(NCP)
  4. 显式关联原则(EAP)
  5. 循环消除原理(CEP)
  6. 类命名原则(CNP)
  7. 熟人打包原则(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


5

是的,这是一个很好的做法,因为它允许您在不发布特定实现的情况下发布接口。就是说,如果您不需要发布外部接口,则将接口定义放在与实现相同的程序包中就没有问题。


2
也许如果您正在为JCP定义接口,或者说这很有意义,但是如果您正在为项目生成这些接口的多个具体实现,那么将这些接口分别打包似乎是不必要的负担。
Brian Reiter

2
无论如何,这并没有太大的负担...
Paul Sonier

4
-1是,是。这使得不必要地难以找到实现并将分离在一起的事物变得困难。
Michael Borgwardt

5
不发布实现是完全不同的事情,可以通过将它们打包私有化来完成-不能通过将它们放在单独的软件包中来完成。没有非公共软件包之类的东西。
Michael Borgwardt

1
而且,这听起来像使所有接口都不必要地公开了。
Adeel Ansari

4

我们在我工作的地方这样做(即:将接口放在一个程序包中,而将实现放在另一个程序包中),而我们得到的主要好处是,我们可以轻松地在实现之间进行交换。


7
它如何帮助您转换实现?
Janusz

看到Cameron Pope的答案,这正是我们在这里这样做的原因
hhafez

3

我没有太多的Java经验,但是我喜欢在C#/。NET中将其作为一种好习惯,因为它允许将来扩展,在这种扩展中,带有实现接口的具体类的程序集可能不会一直分发到客户,因为它们由中间人工厂代理或在Web服务场景中跨线代理。


2

我通常将接口与实现放在一起,但是我可以有点明白为什么您可能希望将它们分开。举例来说,某人想根据您的接口重新实现类,那么他们需要在实现中添加jar / lib / etc,而不仅仅是接口。将它们分开,您只需要说“这是我对该接口的实现”即可完成。就像我说的,不是我做什么,但我可以有点明白为什么有些人会想要。


2

我正在做一个项目,由于以下原因,它对我来说效果很好:

  • 与不同类型的Map或Set相比,单独打包的接口和实现用于不同的用例。没有理由只为树提供一个包(java.util.tree.Map,java.util.tree.Set)。它只是一个标准的数据结构,因此将其与其他数据结构放在一起。但是,如果您要处理的益智游戏具有非常简单的调试界面和漂亮的生产界面,则作为其前端的一部分,您可能会拥有com.your.app.skin.debug和com.your.app .skin.pretty。我不会将它们放在同一个程序包中,因为它们会做不同的事情,而且我知道我会诉诸一些SmurfNamingConvention(DebugAttackSurface,DebugDefenceSurface,PrettyAttackSurface等)为两个人创建一些非正式的命名空间(如果它们位于同一程序包中)。

  • 对于查找单独程序包中的相关接口和实现的问题,我的解决方法是为程序包采用命名配置。例如,我可以将所有接口都放在com.your.app.skin.framework中,并且知道与包树相同级别的其他包是实现。缺点是这是非常规惯例。老实说,我将在6个月的时间内看到此约定的效果:)

  • 我没有虔诚地使用这种技术。有些接口仅在特定的实现中有意义。我不会将它们放在框架包中。在某些程序包中,我看起来好像不会创建40个不同的实现类,因此不必理会。

  • 我的应用程序使用guice并具有很多接口。

程序设计的问题通常涉及利弊,没有一个适合所有人的答案。就像为什么您会在小型200行程序中使用此技术?考虑到我的其他架构选择,对于我的特定问题,这对我来说很有意义。:)


1

我更喜欢它们在同一包装中。毫无意义地创建专门用于接口的程序包。对于只喜欢其接口前缀和后缀(例如Java的“ I”和“ Impl”)的团队来说,这尤其多余。

如果需要将一组接口发布为公共API,则将它们保留在一个完全独立的项目中并创建项目依赖关系更为有意义。但这全都归结为我认为情况的偏好和便利。


0

将它们放在反映您的项目的程序包中。如果接口和实现属于同一项目,则可以将它们组合在一起,这很正常,但是如果您正在编写API,那么其他人可能会选择与其项目相关的软件包名称。

通常,如果是用于同一项目,那么将接口与其impls放在单独的程序包中不会带来任何好处。如果变得杂乱无章,则可能有其他软件包命名问题,而与接口的布置无关。


0

我认为必须注意,对于OSGi框架,最好将它们放在不同的程序包中,以便您可以轻松地导出整个程序包,同时隐藏实现程序包。


-1

有很多充分的理由将接口与实现放在单独的程序包中。例如,您可能想使用支持Dependency Injection的容器在运行时连接必要的实现,在这种情况下,只需在构建时提供接口,并在运行时提供实现即可。或者,您可能希望在运行时提供多个实现(例如,使用模拟实现进行测试)或特定版本的多个版本(例如,用于A / B测试等)。对于此类使用案例,将接口和实现分开打包更为方便。


您无需将实现放在其他程序包中即可执行列出的任何一项操作
Crowie 2013年

这是公平的,但是我仍然认为需求的“在构建时出现与运行时出现”差异是单独包装的一个有力理由。我更新了文本以尝试更清晰地反映这一点。
泰森
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.