有没有(Java)包组织的最佳实践?

228

不久之前,我在这里看到一个有关Java包的细粒度组织的问题。例如,my.project.util, my.project.factory, my.project.service等。

在Java中,有哪些关于包组织和其中内容的最佳实践?

在Java项目中,您如何组织类?

例如,我正在与几个人合作的项目中有一个名为beans的包。它最初是一个包含简单bean的项目,但由于经验不足和时间不足,它最终包含了几乎所有东西。我通过将一些工厂类放入工厂包(具有创建bean的静态方法的类)来对其进行了一些清理,但我们还有其他执行业务逻辑和从属性文件检索代码消息等简单处理(没有业务逻辑)的类。


100
我不同意关闭这个问题。我不是不同意最佳实践问题几乎总是基于经验和观点,但我不同意在Stack Overflow上关闭这些问题。这是良好编程的关键,使用最佳实践并找到最佳解决方案需要这些观点和经验。请重新开放这个问题。 - Cyntech
2
我建议你阅读这篇文章:https://www.satisfice.com/blog/archives/5164。简而言之,整个“最佳实践”的概念已经崩溃了。在最好的情况下,它是一个极其错误的用词,而在最坏的情况下,它甚至会带来危害。 - Stephen C
7个回答

217
我按照特性对软件包进行组织,而不是按照模式或实现角色。我认为像以下这样的软件包是错误的:
  • beans
  • factories
  • collections
我更喜欢下面这种方式:
  • orders
  • store
  • reports
这样我就可以通过“软件包可见性”隐藏实现细节。订单的工厂应该位于orders包中,以便隐藏有关如何创建订单的详细信息。

10
这篇文章提出了关于包可见性的有力观点。虽然我从未见过Java包作用域被使用,但合适的包结构可以让开发人员更好地利用它。 - Jpnh
25
这说得很对,但是很少有开发人员这样做。包应该是由类的内聚集合组成,其中一些类只在包内部可见。这将最小化那些不应该耦合在一起的类之间的耦合,因为它们涉及到不同的特性。按层划分的包方法没有充分利用包可见性修饰符,在这样的项目中,包之间的内聚性低,耦合度高。 - Nate Reed
2
@onof 我也按特征组织我的代码,但是对于所有特征共享的基类,最佳实践是什么? - Marc
我之前一直使用第一种风格,但是现在在处理类时需要跳转到不同的包中,这真的让人感到很糟糕。因此,我现在决定转而使用第二种风格,因为我认为将相关的类放在一起更容易管理。 - M.kazem Akhgary
1
@onof,我知道这是一个旧的答案/问题,但是您对将接口组织在按您建议的结构化软件包中有什么看法? - Renato
显示剩余2条评论

200

包的组织和结构通常是一个热门话题,以下是一些简单的指南,用于包命名和组织结构:

  • 遵循Java 包命名约定
  • 按照功能角色和业务角色来组织你的包
    • 根据功能或模块分解你的包,例如:com.company.product.modulea
    • 进一步地,可以根据软件中的层次来分解。但如果包中只有几个类,那么将所有内容放在一个包中会更合理,例如:com.company.product.module.webcom.company.product.module.util 等。
    • 除非必要,否则不要过度分层。我认为,异常、工厂等应该避免单独打包。
  • 如果你的项目不大,请使用少量的包,例如:com.company.product.modelcom.company.product.util 等。
  • 查看一些流行的开源项目,如Apache 项目,看看它们如何为不同规模的项目进行结构化。
  • 在命名时考虑构建和分发(允许你在不同的包中分发你的 API 或 SDK,参见servlet API)。

经过几次实验和尝试,你应该能够找到一种适合自己的结构。不要固守一种惯例,保持开放心态。


感谢您的回复。这在很大程度上是我们试图涵盖的内容,但是很多不相关的代码进入了我们的bean包中,这也是我的问题所在。 - Cyntech
4
我同意基于功能的软件包是最有意义的。我曾听到一个很好的类比,与公司办公室结构有关。在分层方法中,公司每个级别的个人都会坐在一起,例如高管、经理、行政人员、员工/工人都坐在建筑物的不同区域。这种结构可能不如“功能”组织的方法有效。例如,如果销售主管与销售经理坐在一起,销售员工们也跟着坐在一起,还有一个销售行政人员。这样组织部门可以提供更好的凝聚力。 - alex
同意您的观点。只有一个要强调的问题,关于包中的公司名称,有时候公司被收购或产品被出售。管理层希望删除公司名称。这可能会增加额外的工作量。 - Tushar D

48

简短回答:每个模块/特性一个包,可能带有子包。将密切相关的事物放在同一个包中。避免包之间的循环依赖。

详细回答:我同意这篇文章的大部分观点


2
子包打破了封装。一个“子包”实际上就是一个完全独立的包。 - Ricardo Gamba
链接已损坏。 - plalx

25

我更喜欢将特性放在前面,但这取决于您的项目。请考虑以下要素:

  • 依赖项
    尽量减少包之间的依赖关系,特别是特性之间的依赖。 必要时提取API。
  • 团队组织
    在一些组织中,团队专注于特性,而在其他组织中则专注于层次结构。 这会影响代码的组织方式,可以利用它来规范API或促进合作。
  • 部署和版本控制
    将所有内容放入一个模块中可以使部署和版本控制更简单,但修复错误更加困难。拆分内容可以实现更好的控制、可扩展性和可用性。
  • 对变更的响应
    良好组织的代码比混乱的代码更容易进行更改。
  • 大小(人数和代码行数)
    越大需要越多的规范化/标准化。
  • 重要性/质量
    有些代码比其他代码更重要。API应该比实现更稳定。因此,它们需要清晰地分离。
  • 抽象级别和入口点
    通过查看包树,一个外部人员应该能够了解代码的内容以及从何处开始阅读。

示例:

com/company/module
  + feature1/
    - MainClass          // The entry point for exploring
    + api/               // Public interface, used by other features
    + domain/
      - AggregateRoot
      + api/             // Internal API, complements the public, used by web
      + impl/
    + persistence/
    + web/               // Presentation layer
    + services/          // Rest or other remote API
    + support/
  + feature2/
  + support/             // Any support or utils used by more than on feature
    + io
    + config
    + persistence
    + web

这只是一个示例,比较正式。例如它为feature1定义了两个接口。通常不需要这样做,但如果被不同人以不同的方式使用,这可能是一个好主意。您可以让内部API扩展公共API。

我不喜欢'impl'或'support'这些名称,但它们有助于将不那么重要的内容(领域和API)与重要的内容分开。在命名方面,我喜欢尽可能具体。如果您有一个名为“utils”的包含20个类的包,请将StringUtils移到support/string、HttpUtil移到support/http等。


16
在Java中,包的组织和其中内容的放置是否有最佳实践呢?实际上并没有,有很多想法和意见,但真正的“最佳实践”是运用常识!然而,有一个原则可能被广泛接受。你的包结构应该反映出你的应用程序的(非正式的)模块结构,并且你应该尽量减少(或理想情况下完全避免)模块之间的循环依赖关系。类之间的循环依赖在一个包/模块内是可以的,但是跨包/模块的循环依赖往往使得理解应用程序的架构变得困难,并且可能成为代码重用的障碍。特别是,如果你使用Maven,你会发现循环的跨包/跨模块依赖意味着整个相互连接的混乱必须是一个Maven构件。请阅读No best Practices以了解“最佳实践”和推广它们的人的观点。
我还应该补充一点,即有一个被广泛接受的最佳实践是关于包名的。这就是你的包名应以你组织的域名相反顺序开始。如果你遵循这个规则,你就可以减少因你的(完整)类名与其他人的类名冲突而导致的问题。

12

我看到一些人推崇“按功能打包”而不是“按层次打包”,但是我多年来使用了很多方法,发现“按层次打包”比“按功能打包”要好得多。

此外,我发现一个混合策略:按模块、层次和功能打包的策略在实践中非常有效,因为它具有许多“按功能打包”的优点:

  • 促进可重用框架的创建(具有模型和UI方面的库)
  • 允许插拔式层次实现——在“按功能打包”中几乎不可能,因为它将层次实现放在与模型代码相同的包/目录中。
  • 还有更多...

我在这里详细解释:Java Package Name Structure and Organization,但我的标准包结构是:

revdomain.moduleType.moduleName.layer.[layerImpl].feature.subfeatureN.subfeatureN+1...

其中:

  • revdomain 反转的域名,例如com.mycompany

  • moduleType [应用程序*|框架|工具]

  • moduleName,例如,如果模块类型是应用程序,则为myAppName,如果是会计框架,则为'finance'

  • layer [模型|用户界面|持久化|安全等等]

  • layerImpl,例如wicket、jsp、jpa、jdo、hibernate(注意:如果层是模型,则不使用)

  • feature,例如,财务

  • subfeatureN,例如,会计

  • subfeatureN+1,例如,折旧

*有时,如果moduleType是应用程序,则省略'app',但将其放入其中可以使所有模块类型的包结构保持一致。


7
我不清楚包组织的标准实践。通常我会创建覆盖某个合理宽度范围的包,但在一个项目中可以进行区分。例如,我目前正在开发的个人项目有一个专门用于自定义 UI 控件(包含许多子类化 swing 类的类)的包,我有一个专门用于数据库管理的包,还有一个用于一组我创建的监听器/事件的包等等。
另一方面,我的一位同事为他所做的几乎每件事都创建了一个新的包。他想要的每个不同的 MVC 都有其自己的包,似乎 MVC 集是唯一允许存在于同一个包中的类组。我记得他曾经有过 5 个不同的包,每个包里只有一个类。我认为他的方法有点极端(当我们简单地无法处理时,团队迫使他减少包数),但对于一个非微不足道的应用程序来说,把所有东西放在同一个包中也是这样。这是你和你的团队必须找到的平衡点。
你可以尝试退后一步并思考:如果你是项目的新成员,或者你的项目发布为开源或 API,那么找到你想要的东西有多容易/困难?因为对我来说,这才是我真正想要的包的目的:组织。类似于我在计算机上对文件进行文件夹存储,我期望能够再次找到它们,而无需搜索整个驱动器。我期望能够在不必搜索包中所有类列表的情况下找到所需的类。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接