在 Java 项目中使用什么策略进行包命名,为什么?[已关闭]

2022-08-31 11:18:42

我不久前想过这个问题,最近当我的商店正在做第一个真正的Java Web应用程序时,它重新浮出水面。

作为介绍,我看到两个主要的包命名策略。(需要明确的是,我指的不是整个'domain.company.project'部分,我说的是它下面的包约定。无论如何,我看到的包命名约定如下:

  1. 功能:根据包在体系结构上的功能命名包,而不是根据业务域命名包的标识。另一个术语可能是根据“层”命名。因此,您将有一个 *.ui 包、一个 *.域包和一个 *.orm 包。您的包裹是水平切片,而不是垂直切片。

    这比逻辑命名更常见。事实上,我不相信我曾经见过或听说过一个项目这样做。这当然让我感到怀疑(有点像认为你已经想出了NP问题的解决方案),因为我不是很聪明,我认为每个人都必须有充分的理由这样做。另一方面,我不反对人们只是错过了房间里的大象我从来没有听说过以这种方式进行包装命名的实际论点。它似乎只是事实上的标准。

  2. 逻辑:根据包的业务域标识命名包,并将与该垂直功能切片相关的每个类放入该包中。

    正如我之前提到的,我从未见过或听说过这一点,但这对我来说很有意义。

    1. 我倾向于垂直而不是水平接近系统。我想进入并开发订单处理系统,而不是数据访问层。显然,在该系统的开发过程中,我很有可能会触及数据访问层,但关键是我不这么认为。当然,这意味着,当我收到变更单或想要实现一些新功能时,最好不要为了找到所有相关的类而在一堆包中钓鱼。相反,我只是查看X包,因为我正在做的事情与X有关。

    2. 从开发的角度来看,我认为让你的软件包记录你的业务领域而不是你的架构是一个重大的胜利。我觉得领域几乎总是系统中更难摸索的部分,因为系统的架构,特别是在这一点上,在实现中几乎变得平凡。事实上,我可以进入一个具有这种命名约定的系统,并且从软件包的命名中立即知道它处理订单,客户,企业,产品等似乎非常方便。

    3. 这似乎可以让你更好地利用Java的访问修饰符。这使您可以更干净地将接口定义到子系统中,而不是系统层中。因此,如果你有一个想要透明持久化的订单子系统,理论上你可以永远不要让其他人知道它是持久的,因为不必在 dao 层中创建其持久化类的公共接口,而是将 dao 类打包在只包含它所处理的类中。显然,如果你想公开这个功能,你可以为它提供一个接口或让它公开。似乎通过将系统功能的垂直切片拆分到多个包中来丢失很多这些。

    4. 我想我能看到的一个缺点是,它确实使撕裂图层变得更加困难。您不必只是删除或重命名包,然后使用备用技术将新包放到适当的位置,而是必须进入并更改所有包中的所有类。但是,我不认为这有什么大不了的。这可能是由于缺乏经验,但我必须想象,与您在系统中编辑垂直特征切片的次数相比,您交换技术的次数相形见绌。

所以我想这个问题会向你提出,你如何命名你的包裹,为什么?请理解,我不一定认为我在这里偶然发现了金鹅或其他东西。我对这一切很陌生,主要是学术经验。但是,我无法发现我推理中的漏洞,所以我希望你们都能,这样我就可以继续前进。


答案 1

对于包设计,我首先按层划分,然后按其他一些功能划分。

还有一些其他规则:

  1. 图层从最常规(底部)堆叠到最特定(顶部)
  2. 每层都有一个公共接口(抽象)
  3. 一个层只能依赖于另一个层的公共接口(封装)
  4. 一个层只能依赖于更通用的层(从上到下的依赖关系)
  5. 一层最好取决于它正下方的层

因此,例如,对于 Web 应用程序,您可以在应用程序层中具有以下层(从上到下):

  • 表示层:生成将在客户端层中显示的 UI
  • 应用程序层:包含特定于应用程序的有状态逻辑
  • 服务层:按域、无状态对功能进行分组
  • 集成层:提供对后端层(数据库、jms、电子邮件等)的访问

对于生成的包布局,以下是一些附加规则:

  • 每个包名称的根是<prefix.company>.<appname>.<layer>
  • 层的接口按功能进一步拆分:<root>.<logic>
  • 层的私有实现以私有为前缀:<root>.private

下面是一个示例布局。

表示层按视图技术划分,并按(组)应用程序进行划分。

com.company.appname.presentation.internal
com.company.appname.presentation.springmvc.product
com.company.appname.presentation.servlet
...

应用层分为多个用例。

com.company.appname.application.lookupproduct
com.company.appname.application.internal.lookupproduct
com.company.appname.application.editclient
com.company.appname.application.internal.editclient
...

服务层被划分为多个业务域,受后端层中的域逻辑的影响。

com.company.appname.service.clientservice
com.company.appname.service.internal.jmsclientservice
com.company.appname.service.internal.xmlclientservice
com.company.appname.service.productservice
...

集成层分为“技术”和访问对象。

com.company.appname.integration.jmsgateway
com.company.appname.integration.internal.mqjmsgateway
com.company.appname.integration.productdao
com.company.appname.integration.internal.dbproductdao
com.company.appname.integration.internal.mockproductdao
...

像这样分离包的优点是更容易管理复杂性,并且提高了可测试性和可重用性。虽然这似乎有很多开销,但根据我的经验,它实际上非常自然,每个在这个结构(或类似结构)上工作的人都会在几天内把它捡起来。

为什么我认为垂直方法不是那么好?

在分层模型中,几个不同的高级模块可以使用相同的较低级别模块。例如:您可以为同一应用程序构建多个视图,多个应用程序可以使用同一个服务,多个服务可以使用同一个网关。这里的诀窍是,在层中移动时,功能级别会发生变化。更具体层中的模块不会在更一般层的模块上映射 1-1,因为它们表达的功能级别不会映射 1-1。

当您使用垂直方法进行包设计时,即首先按功能划分,然后将所有具有不同功能级别的构建块强制放入相同的“功能夹克”中。您可以为更具体的模块设计常规模块。但这违反了一个重要的原则,即更一般的层不应该知道更具体的层。例如,服务层不应根据应用层的概念进行建模。


答案 2

我发现自己坚持鲍勃叔叔的包装设计原则。简而言之,要一起重用和一起更改的类(出于相同的原因,例如依赖关系更改或框架更改)应该放在同一个包中。IMO,在大多数应用程序中,功能细分将比垂直/业务特定细分有更好的机会实现这些目标。

例如,域对象的水平切片可以被不同类型的前端甚至应用程序重用,而当需要更改底层Web框架时,Web前端的水平切片可能会一起更改。另一方面,如果跨不同功能区域的类分组到这些包中,则很容易想象这些更改在许多包中的连锁反应。

显然,并非所有类型的软件都是相同的,在某些项目中,垂直细分可能是有意义的(在实现可重用性和接近变化的目标方面)。