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


88

不久前,我就想到了这一点,最近,当我的商店正在制作其第一个真正的Java Web应用程序时,它又浮出水面。

作为介绍,我看到了两种主要的软件包命名策略。(要清楚,我不是在指整个“ domain.company.project”部分,而是在讨论其下​​的包约定。)无论如何,我看到的包命名约定如下:

  1. 功能性:根据体系结构上的功能来命名软件包,而不是根据业务领域来标识它们的身份。 对此的另一个术语可能是根据“层”进行命名。因此,您将拥有一个* .ui包,一个* .domain包和一个* .orm包。您的包裹是水平切片,而不是垂直切片。

    这是比逻辑命名更常见。实际上,我不相信我曾经见过或听说过执行此操作的项目。当然,这让我感到不安(有点像认为您已经提出了NP问题的解决方案),因为我并不聪明,我认为每个人都必须有充分的理由以自己的方式去做。在另一方面,我不反对人们只是缺少在房间里的大象我从来没有听说过的实际参数做包命名这种方式。这似乎只是事实上的标准。

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

    正如我之前提到的,我从未见过或听说过,但这对我来说意义非凡。

    1. 我倾向于垂直而不是水平地接近系统。我想开发订单处理系统,而不是数据访问层。显然,我很有可能在该系统的开发过程中接触到数据访问层,但是重点是我不这么认为。当然,这意味着当我收到变更单或想要实现一些新功能时,不必去大量的软件包中寻找所有相关的类,这将是很好的。取而代之的是,我只是查看X软件包,因为我所做的与X有关。

    2. 从开发的角度来看,我认为使软件包记录您的业务领域而不是体系结构是一个重大胜利。我觉得域几乎总是系统的一部分,这很难理解,因为系统的体系结构(尤其是在这一点上)在其实现中几乎变得平凡。我可以使用这种命名约定进入系统,并立即从软件包的命名中得知它处理订单,客户,企业,产品等的事实,这似乎很方便。

    3. 看来这将使您能够更好地利用Java的访问修饰符。这使您可以更加清晰地将接口定义为子系统,而不是系统层。因此,如果您有想要透明透明地持久化的orders子系统,那么您不必通过在dao层中为其持久性类创建公共接口,而不必将其打包在里面,就可以从理论上讲永远不会让其他任何人知道它是持久性的。仅涉及其处理的类。显然,如果您想公开此功能,则可以为其提供接口或将其公开。通过将系统功能的垂直部分分割成多个软件包,您似乎损失了很多。

    4. 我想我可以看到的一个缺点是,它确实会使撕裂图层变得更加困难。您必须进入并更改所有软件包中的所有类,而不仅仅是删除或重命名软件包,然后使用替代技术将新软件包放到适当的位置。但是,我认为这没什么大不了的。可能是由于缺乏经验,但我必须想象一下,与您在系统中进行垂直要素切片编辑的时间相比,交换技术的时间显得苍白。

因此,我想这个问题会传给您,您如何命名包裹以及为什么?请理解,我不一定非要偶然发现这里的金鹅或其他东西。我对这一切非常陌生,主要是具有学术经验。但是,我无法发现推理中的漏洞,因此希望大家能够继续前进。


到目前为止,我所听到的似乎表明这是一种判断。但是,大多数答案并未将重点放在实际考虑上。这就是我想要的。到目前为止,感谢您的帮助!
Tim Visher,2009年

我不认为这是一个判断电话。首先按层划分,然后按功能划分绝对是必经之路。我正在更新我的答案。
eljenso

@eljenso:告诉Eclipse的人:)在eclipse中,第一个划分是“功能”,它是部署的单元,也是功能。在“功能”中,有“插件”,通常按层划分-但是,同一“功能”中的插件必须始终一起部署。如果只有一个部署单元,则可以先按层划分,然后再按功能划分。对于多个部署单元,您不希望仅部署表示层-您需要其他功能。
PeterŠtibraný2011年

Answers:


32

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

还有一些其他规则:

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

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

  • 表示层:生成将在客户端层中显示的UI
  • 应用层:包含特定于应用的逻辑,有状态
  • 服务层:按域分组功能,无状态
  • 集成层:提供对后端层(db,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上映射。

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


“这样分离软件包的优势在于,它更易于管理复杂性,并提高了可测试性和可重用性。” 您真的不知道为什么会这样。
mangoDrunk 2011年

1
您无需了解为何并非如此。但是无论如何……组织的规则非常清晰明了,因此降低了复杂性。由于组织本身,它更具可测试性和可重用性。每个人都可以尝试一下,之后我们可以同意或不同意。我简要解释了垂直方法的一些缺点,隐含地给出了我认为水平方法更容易的原因。
eljenso 2011年

8
本文很好地解释了为什么最好使用逐功能而不是逐层包
汤姆(Tom)

@eljenso“您不了解为何不是这样的原因”,试图转移举证责任?我认为,汤姆(Tom)发表的文章很好地解释了为什么按功能打包更好,而我却很难接受“我不认为这是一个判断电话。首先按层划分,然后按功能划分绝对是方法去”。
pka

18

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

例如,域对象的水平切片可以由不同种类的前端甚至应用程序重用,并且当需要更改基础Web框架时,Web前端的水平切片可能会一起更改。另一方面,如果将不同功能区域中的类分组在许多程序包中,则很容易想象这些更改在许多程序包中产生的连锁反应。

显然,并非所有软件都是相同的,并且在某些项目中,垂直细分可能是有意义的(就实现可重用性和变更的接近性而言)。


谢谢,这很清楚,正如我在问题中所说的那样,我觉得您交换技术的次数远远少于在垂直功能方面转移的次数。这不是您的经验吗?
Tim Visher,2009年

这不仅仅是关于技术。仅当垂直切片是独立的应用程序/服务通过某个接口(如果需要的话,可以通过SOA)相互通信时,原始帖子的第1点才有意义。下面还有更多...
Buu Nguyen,2009年

现在,当您进入每个具有自己的gui / business / data的细粒度应用程序/服务的详细信息时,我几乎无法想象垂直切片中的变化,包括有关技术,依赖性,规则/工作流,日志记录的变化,安全性,UI样式可以与其他部分完全隔离。
阮阮

我会对您对我的回答
i3ensays 2013年

@BuuNguyen到包装设计原理的链接已断开。
johnnieb '16

5

通常存在两种划分级别。从顶部开始,有部署单元。这些被命名为“逻辑上”(按您的说法,请考虑Eclipse功能)。在部署单元内部,您可以对软件包进行功能划分(例如Eclipse插件)。

例如,特征是com.feature,它包括com.feature.clientcom.feature.corecom.feature.ui插件。在插件内部,我对其他软件包的划分很少,尽管这也不罕见。

更新:顺便说一句,Juergen Hoeller在InfoQ上有很多关于代码组织的演讲:http : //www.infoq.com/presentations/code-organization-large-projects。于尔根(Juergen)是Spring的建筑师之一,对这方面的知识非常了解。


我不太明白。通常,您可能会看到com.apache.wicket.x,其中x是功能性的或逻辑性的。我通常看不到com.x。我猜你是说要使用com.company.project.feature.layer结构?你有什么理由吗?
Tim Visher,2009年

原因是“ com.company.project.feature”是部署单元。在此级别上,某些功能是可选的,可以跳过(即未部署)。但是,在内部功能中,事情不是可选的,您通常都希望全部。在这里,按层划分更有意义。
PeterŠtibraný09年

4

我研究过的大多数Java项目都是先按功能对Java包进行切片,然后再按逻辑进行切片。

通常,部件足够大,以至于将它们分解为单独的构建工件,您可以在其中将核心功能放入一个jar中,将api放入另一个jar中,将Web前端的内容放入warfile中,等等。


看起来像什么?domain.company.project.function.logicalSlice?
Tim Visher,2009年

差不多!egdcpweb.profiles,dcpweb.registration,dcpapis,dcppersistence等通常效果很好,您的里程可能会有所不同。还取决于您是否使用域建模-如果您有多个域,则可能要先按域拆分。
本·哈迪

我个人更喜欢相反的,逻辑的然后是功能的。apples.rpc,apples.model,然后banana.model,banana.store
mP。

能够将所有Apple东西组合在一起比将所有Web东西组合在一起具有更大的价值。
mP。

3

软件包将作为一个单元进行编译和分发。在考虑包中包含哪些类时,关键条件之一就是其依赖性。该类还依赖其他哪些软件包(包括第三方库)。一个组织良好的系统会将具有相似依赖性的类聚类到一个程序包中。这限制了一个库中更改的影响,因为只有少数定义良好的程序包将依赖它。

听起来您的逻辑垂直系统可能倾向于“涂抹”大多数软件包中的依赖项。也就是说,如果每个功能都打包为垂直切片,则每个程序包将取决于您使用的每个第三方库。对库的任何更改都可能会影响整个系统。


啊。因此,进行水平切片的一件事是使您免于正在使用的库中的实际升级。我认为您是对的,垂直切片抹平了系统对每个软件包的依赖。为什么这有什么大不了的?
Tim Visher,2009年

对于“功能”来说,没什么大不了的。它们往往更加不稳定,并且具有更多的依赖性。另一方面,此“客户端”代码往往具有较低的可重用性。对于要成为可重用库的事物,将客户与每一个小小的变化隔离开都是一项巨大的投资。
erickson

3

我个人更喜欢按逻辑对类进行分组,然后在其中对每个功能参与都包括一个子包。

包装目标

毕竟,包是关于将事物分组在一起的,即相关类之间的思想彼此接近。如果他们住在同一个包中,则可以利用私有包来限制可见性。问题在于,将您所有的观点和毅力集中到一个包中,可能导致许多类混合到一个包中。因此,下一个明智的选择是创建视图,持久性,util子包并相应地重构类。不幸的是,受保护和程序包专用作用域不支持当前程序包和子程序包的概念,因为这将有助于执行此类可见性规则。

我现在看到通过功能实现分离的价值,是将所有与视图相关的东西分组的价值是什么。这种命名策略中的内容与视图中的某些类断开了连接,而其他类则具有持久性,依此类推。

我的逻辑包装结构的一个例子

为了说明的目的,让我们命名两个模块-将名称模块用作将类分类为包装树的特定分支下的概念。

apple.model apple.store香蕉.model.banana.store

优点

使用Banana.store.BananaStore的客户端只能使用我们希望提供的功能。休眠版本是一个实现细节,他们无需知道这些类,也不必看到这些类,因为它们会给存储操作增加混乱。

其他逻辑v功能优势

到根的距离越远,范围就越广,属于一个包的事物开始表现出对属于其他模块的事物的依赖性越来越大。例如,如果要检查“香蕉”模块,则大多数依赖项将限于该模块内。实际上,在该软件包范围之外,根本不会引用“香蕉”下的大多数帮助程序。

为什么要功能?

通过基于功能组合事物可以实现什么价值。在这种情况下,大多数类彼此独立,几乎不需要使用包私有方法或类。将它们重构为自己的子包几乎没有好处,但确实有助于减少混乱。

开发人员对系统的更改

当开发人员的任务是进行琐碎的更改时,似乎很愚蠢的是,他们所做的更改可能包含了包树所有区域的文件。使用逻辑结构化方法,它们的更改在软件包树的同一部分中更本地化,这似乎是正确的。


“当开发人员从包树的所有区域下载文件时。当您说这似乎很愚蠢时,您是对的。因为这正是正确分层的重点:更改不会在应用程序的整个结构中引起涟漪。
eljenso

谢谢你的建议。我不确定我是否清楚地了解您。我相信您正在尝试使用逻辑包,但我不清楚原因为何。您能否使您的答案更清楚一点,也许重新措辞?谢谢!
Tim Visher,2009年

3

两个官能(建筑)和逻辑(功能)接近包装有一个地方。许多示例应用程序(可在教科书中找到的示例程序)都遵循将演示文稿,业务服务,数据映射和其他体系结构层放入单独的程序包中的功能方法。在示例应用程序中,每个包通常只有几个或一个类。

最初的方法很好,因为人为设计的示例经常用于:1)从概念上映射出所呈现框架的体系结构,2)出于单一逻辑目的(例如,从诊所添加/删除/更新/删除宠物)来完成此操作。问题在于,许多读者将此视为没有界限的标准。

随着“业务”应用程序的扩展以包括越来越多的功能,遵循功能方法成为一种负担。尽管我知道在哪里可以找到基于体系结构层的类型(例如,在“ Web”或“ UI”程序包下的Web控制器等),但是开发单个逻辑功能开始需要在许多程序包之间来回跳转。至少这很麻烦,但是比这还糟。

由于逻辑相关类型未打包在一起,因此API公开过度;逻辑相关类型之间的交互被强制为“公共”,以便类型可以相互导入和交互(最小化默认值/程序包可见性的能力已消失)。

如果我要构建框架库,则我的软件包必须遵循功能/体系结构包装方法。我的API使用者甚至可能会赞赏他们的import语句包含以体系结构命名的直观包。

相反,在构建业务应用程序时,我将按功能打包。我将Widget,WidgetService和WidgetController都放在同一个“ com.myorg.widget。 ”包中,然后利用默认可见性(并且具有更少的import语句以及包间依赖关系)没有问题。

但是,有交叉的情况。如果我的WidgetService被许多逻辑域(功能)使用,我可能会创建一个“ com.myorg.common.service。 ”包。我也很有可能创建旨在跨功能重复使用的类,并最终获得诸如“ com.myorg.common.ui.helpers。 ”和“ com.myorg.common.util。 ”之类的包。我什至可能最终将所有这些后来的“公共”类移到一个单独的项目中,并将它们作为myorg-commons.jar依赖项包含在我的业务应用程序中。


2

这取决于您的逻辑流程的粒度吗?

如果它们是独立的,则通常会在源代码管理中为其提供一个新项目,而不是一个新软件包。

我目前正在从事的项目正朝着逻辑拆分的方向发展,有一个用于jython方面的软件包,一个用于规则引擎的软件包,一个用于foo,bar,binglewozzle的软件包等。我正在寻找具有XML特定的解析器的软件。 / writers用于该程序包中的每个模块,而不是拥有XML程序包(我之前已经做过),尽管仍然存在共享逻辑可用的核心XML程序包。但是,这样做的一个原因是它可能是可扩展的(插件),因此每个插件也将需要定义其XML(或数据库等)代码,因此将其集中化可能在以后引入问题。

最后,对于特定项目而言,这似乎是最明智的选择。我认为按照典型的项目分层图的方式打包很容易。最后,您将混合逻辑和功能包装。

需要的是带标签的名称空间。可以为Jython和XML标记用于某些Jython功能的XML解析器,而不必选择两者之一。

也许我在摇晃。


我不完全明白你的意思。我认为您是在说什么,您应该做对您的项目最有意义的事情,并且对您来说这是合乎逻辑的,并添加一些功能。这样做有具体的实际原因吗?
Tim Visher,2009年

正如我所说,我将拥有一些插件来增强内置功能。一个插件将必须能够解析和写入其XML(并最终写入数据库)并提供功能。因此,我是否拥有com.xx.feature.xml或com.xx.xml.feature?前者看起来更整洁。
JeeBee

2

我尝试以如下方式设计包结构:如果要绘制依赖关系图,则很容易遵循并使用一致的模式,并尽可能减少循环引用。

对我来说,这在垂直命名系统中而不是在水平命名系统中更容易维护和可视化。如果component1.display具有对component2.dataaccess的引用,则比display.component1具有对dataaccess的引用引发更多的警告。组件2。

当然,两者共享的组件都放在自己的程序包中。


据我了解,您将提倡垂直命名约定。当您需要跨多个片的类时,我认为这就是* .utils包的用途。
Tim Visher,2009年

2

我完全遵循并提出合乎逻辑的(“按功能”)组织!程序包应尽可能遵循“模块”的概念。职能组织可能会将模块扩展到整个项目中,从而导致封装较少,并且易于更改实现细节。

让我们以一个Eclipse插件为例:将所有视图或动作放在一个包中会很混乱。取而代之的是,功能的每个组件都应转到功能的程序包中,如果有的话,应放入子程序包中(featureA.handlers,featureA.preferences等)。

当然,问题出在分层包系统(包括Java在内)中,这使得正交问题不可能或至少非常困难地处理-尽管它们无处不在!


1

我个人会去进行功能命名。简短的原因:它避免了代码重复或依赖项梦night。

让我详细说明一下。当您使用带有其自己的包树的外部jar文件时,会发生什么?您正在有效地将(编译的)代码导入到您的项目中,并带有(功能上分开的)包树。同时使用两个命名约定是否有意义?不,除非您不知道。就是说,如果您的项目足够小并且只有一个组件。但是,如果您有多个逻辑单元,则可能不想重新实现数据文件加载模块。您想在逻辑单元之间共享它,在逻辑上不相关的单元之间没有人为的依赖性,也不必选择要将特定共享工具放入哪个单元。

我想这就是为什么函数命名是在达到或打算达到一定规模的项目中最常使用的原因,而在类命名约定中使用逻辑命名来跟踪特定角色(如果每个类中的每个类都有)包。

我将尝试更准确地回应您在逻辑命名上的每个观点。

  1. 如果您在计划变更时不得不去老旧的类中修改功能,那么这表明抽象性很差:您应该构建提供明确定义的功能的类,并用一句话来定义。只有少数顶级类可以组合所有这些内容以反映您的商业智能。这样,您将能够重用更多代码,更轻松的维护,更清晰的文档和更少的依赖性问题。

  2. 这主要取决于您处理项目的方式。逻辑和功能视图绝对是正交的。因此,如果使用一种命名约定,则需要将另一种命名规则应用于类名以保持某种顺序,或者在某种程度上将一种命名约定派生到另一种命名约定。

  3. 访问修饰符是允许其他理解您的处理的类访问您的类的好方法。逻辑关系并不意味着了解算法或并发约束。功能可以,尽管事实并非如此。我对除公共和私有之外的访问修饰符非常厌倦,因为它们常常掩盖了缺乏适当的架构和类抽象的问题。

  4. 在大型的商业项目中,不断变化的技术比您想象的要频繁发生。例如,我不得不更改XML解析器的3倍,更改缓存技术的2倍和更改地理定位软件的2倍。好东西,我把所有坚韧的细节藏在一个专用的包装里。


1
如果我错了,请原谅我,但这听起来像是您正在谈论开发旨在供许多人使用的框架。我认为规则在这种类型的开发与开发最终用户系统之间有所变化。我错了吗?
Tim Visher,2009年

我认为对于许多垂直切片使用的通用类,拥有像utils包装这样的东西是有意义的。然后,任何需要它的类都可以简单地依赖于该包。
Tim Visher,2009年

同样,规模很重要:当一个项目足够大时,它需要几个人的支持,并且会发生某种专业化。为了简化交互并防止错误,将项目分成多个部分变得非常必要。而且utils软件包很快就会变得庞大起来!
瓦汉2009年

1

根本不使用软件包(根软件包除外)是一个有趣的实验。

然后出现的问题是,何时以及为什么引入软件包才有意义。据推测,答案将与项目开始时的答案有所不同。

我想您的问题根本就出现了,因为包就像类别,有时很难决定一个或另一个。有时,最好使用标签来传达类在许多情况下可用的信息。


0

从纯实际的角度来看,java的可见性构造允许同一包中的类访问具有protecteddefault可见性的方法和属性public。从完全不同的代码层使用非公共方法肯定会引起很大的代码异味。因此,我倾向于将来自同一层的类放入同一包中。

我很少在其他地方使用这些受保护的方法或默认方法(可能在类的单元测试中除外),但是当我这样做时,它总是来自同一层的类


始终依赖于下一层不是多层系统的本质吗?例如,UI取决于服务层,而服务层又取决于域层,等等。将垂直切片打包在一起似乎可以防止过多的包间依赖关系,不是吗?
Tim Visher,2009年

我几乎从未使用过受保护的默认方法。99%是公共或私人的。一些例外:(1)仅由单元测试使用的方法的默认可见性;(2)受保护的抽象方法,该方法仅可用于抽象基类。
Esko Luontola,2009年

1
蒂姆·维瑟(Tim Visher),程序包之间的依赖关系不是问题,只要依赖关系始终指向同一方向,并且依赖关系图中没有循环。
Esko Luontola,2009年

0

这取决于。在我的工作范围中,有时我们按功能(数据访问,分析)或资产类别(贷方,股票,利率)拆分软件包。只需选择最适合您团队的结构即可。


有什么理由可以选择两种方式吗?
Tim Visher,2009年

按资产类别划分(更一般而言:按业务领域划分)使新人们更容易找到代码。按功能拆分很适合封装。对我而言,Java中的“包”访问类似于C ++中的“朋友”访问。
–quant_dev

@quant_dev我不同意“ ..by函数适合封装”。我认为您的意思完全相反。另外,不要仅仅从“便利”中选择。每个都有一个案例(请参阅我的答案)。
i3ensays 2013年

-3

根据我的经验,可重用性比解决方案会产生更多的问题。使用最新的廉价处理器和内存,我宁愿重复代码而不是紧密集成以便重用。


5
代码重用与硬件价格无关,而与代码维护成本有关。
user2793390

1
同意,但是在简单的模块中修复某些问题不会影响整个代码。您在说什么维护费用?
Raghu Kasturi 2014年

1
模块中的错误修复将影响整个应用程序。您为什么要多次修复同一错误?
user2793390 2014年
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.