我前段时间考虑过这个问题,最近我的商店正在开发第一个真正的Java网络应用程序。
作为介绍,我看到两个主要的包命名策略。 (要清楚,我不是指整个'domain.company.project'的一部分,我正在讨论它下面的包约定。)无论如何,我看到的包命名约定如下:< / p>
功能:根据业务领域,根据其功能在架构上命名您的包,而不是根据业务域命名。根据“层”命名的另一个术语可能是命名。所以,你有一个* .ui包和一个* .domain包和一个* .orm包。你的包是水平切片而不是垂直切片。
这比逻辑命名更常见 。事实上,我不相信我曾经见过或听说过这样做的项目。这当然让我很害羞(有点像认为你已经想出了NP问题的解决方案),因为我不是非常聪明,我认为每个人都必须有充分的理由按照他们的方式去做。另一方面,我并不反对人们只是错过了房间里的大象和我从来没有听说过的实际参数以这种方式进行包装命名。它似乎只是事实上的标准。
逻辑:根据业务域标识命名您的软件包,并将与该垂直功能片段有关的每个类放入该软件包中。
正如我之前提到的,我从未见过或听说过这一点,但这对我来说很有意义。
我倾向于垂直而不是水平地接近系统。我想进入并开发订单处理系统,而不是数据访问层。显然,我很有可能在该系统的开发中触及数据访问层,但重点是我不这么认为。当然,这意味着当我收到变更单或者想要实现一些新功能时,不必为了找到所有相关的类而不必在一堆包中钓鱼。相反,我只是查看X包,因为我正在做的事情与X有关。
从开发的角度来看,我认为让您的软件包记录您的业务领域而非您的架构是一项重大胜利。我觉得这个领域几乎总是系统的一部分,因为系统的体系结构,特别是在这一点上,它的实现几乎变得平凡,因此很难理解。事实上,我可以通过这种类型的命名约定进入系统,并立即从包的命名知道它处理订单,客户,企业,产品等,这似乎非常方便。
看起来这样可以让您更好地利用Java的访问修饰符。这使您可以更清晰地将接口定义到子系统中,而不是定义到系统的各个层中。因此,如果你有一个你想要透明持久的订单子系统,理论上你永远不会让任何其他东西知道它是持久的,因为不必在dao层中为其持久性类创建公共接口,而是将dao类包装在只有它处理的类。显然,如果你想公开这个功能,你可以为它提供一个接口或公开它。通过将系统功能的垂直切片分成多个包,您似乎失去了很多。
我认为我可以看到的一个缺点是,它确实会使层剥离变得更加困难。您必须进入并更改所有软件包中的所有类,而不是仅删除或重命名软件包,然后使用备用技术删除新软件包。但是,我不认为这是一个大问题。这可能源于缺乏经验,但我必须想象,与您进入和编辑系统中垂直特征切片的次数相比,您更换技术的次数相形见绌。
所以我想问题会告诉你,你如何命名你的包裹以及为什么?请理解我并不一定认为我偶然发现了金鹅或其他东西。我对这一切都很陌生,主要是学术经历。但是,我无法发现我的推理中的漏洞,所以我希望你们都可以继续前进。
答案 0 :(得分:33)
对于包装设计,我首先分层,然后是其他功能。
还有一些额外的规则:
因此,对于Web应用程序,您可以在应用程序层中使用以下层(从上到下):
对于生成的包布局,这些是一些额外的规则:
<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。
当您使用垂直方法进行包设计时,即首先按功能划分,然后强制所有具有不同级别功能的构建块进入相同的“功能夹克”。您可以为更具体的模块设计通用模块。但是这违反了更普遍的层不应该知道更具体的层的重要原则。例如,服务层不应该在应用层的概念之后建模。
答案 1 :(得分:18)
我发现自己坚持使用鲍勃叔叔package design principles。简而言之,要一起重用并一起更改的类(出于同样的原因,例如依赖项更改或框架更改)应该放在同一个包中。 IMO,功能分解比大多数应用程序中的垂直/业务特定分解更有可能实现这些目标。
例如,域对象的水平切片可以由不同类型的前端甚至应用程序重用,并且当需要更改底层Web框架时,Web前端的水平切片可能会一起更改。另一方面,如果跨不同功能区域的类被分组在这些包中,很容易想象这些变化在许多包中产生的连锁反应。
显然,并非所有类型的软件都是相同的,并且在某些项目中,垂直细分可能是有意义的(在实现可重用性和可接近变化的目标方面)。
答案 2 :(得分:5)
通常存在两种级别的划分。从顶部开始,有部署单元。这些被命名为“逻辑”(在您的术语中,认为Eclipse功能)。在部署单元内部,您可以对软件包进行功能划分(想想Eclipse插件)。
例如,功能为com.feature
,它由com.feature.client
,com.feature.core
和com.feature.ui
个插件组成。在插件内部,我对其他软件包的分工很少,尽管这也不常见。
更新:顺便说一句,Juergen Hoeller关于InfoQ代码组织的热烈讨论:http://www.infoq.com/presentations/code-organization-large-projects。 Juergen是Spring的建筑师之一,对这些东西了如指掌。
答案 3 :(得分:4)
我参与过的大多数java项目首先在功能上对java包进行切片,然后是逻辑上的。
通常部分足够大,以至于它们被分解为单独的构建工件,您可以将核心功能放入一个jar,将apis放入另一个jar,将Web前端放入warfile等等。
答案 4 :(得分:3)
包将作为一个单元进行编译和分发。在考虑包中属于哪些类时,其中一个关键标准是其依赖性。此类所依赖的其他包(包括第三方库)。组织良好的系统将在包中集群具有相似依赖性的类。这限制了一个库中更改的影响,因为只有少数明确定义的包依赖于它。
听起来你的逻辑垂直系统可能会在大多数软件包中“涂抹”依赖关系。也就是说,如果每个功能都打包为垂直切片,则每个包都将取决于您使用的每个第三方库。对库的任何更改都可能会影响整个系统。
答案 5 :(得分:3)
我个人更喜欢逻辑上对类进行分组,然后在其中包括每个功能参与的子包。
套件毕竟是关于将事物分组在一起的 - 相关类的想法彼此接近。如果他们住在同一个包中,他们可以利用私有包来限制可见性。问题是将所有视图和持久性内容集中到一个包中会导致很多类混合到一个包中。因此,下一个明智的做法是创建视图,持久性,util子包和重构类。遗憾的是,受保护和包私有作用域不支持当前包和子包的概念,因为这将有助于实施此类可见性规则。
我现在看到了通过功能分离的价值,因为有什么价值可以将所有与视图相关的东西分组。这个命名策略中的内容与视图中的某些类断开连接,而其他类则持续存在等等。
出于说明的目的,我们可以命名两个模块 - 使用名称模块作为在pacckage树的特定分支下对类进行分组的概念。
apple.model 苹果商店 banana.model banana.store使用Banana.store.BananaStore的客户端仅暴露于我们希望提供的功能。 hibernate版本是一个实现细节,他们不需要知道也不应该看到这些类,因为它们会增加存储操作的混乱。
越往根越大,范围变得越宽,属于一个包的东西开始表现出对属于其他模块的东西越来越多的依赖。如果要检查例如“香蕉”模块,则大多数依赖关系将限于该模块内。事实上,在这个包装范围之外,根本不会引用“banana”下的大多数助手。
通过基于功能集中来实现什么值。在这种情况下,大多数类是彼此独立的,很少或根本不需要利用包私有方法或类。将它们重构到自己的子包中几乎没有收获,但确实有助于减少混乱。
当开发人员的任务是进行一些微不足道的更改时,他们可能会发生包含来自包树所有区域的文件的更改,这似乎很愚蠢。使用逻辑结构化方法,它们的更改在包树的同一部分中更加本地化,这似乎是正确的。
答案 6 :(得分:3)
功能性 (架构)和 逻辑 (功能)包装方法都占有一席之地。许多示例应用程序(在教科书等中找到的应用程序)遵循将表示,业务服务,数据映射和其他架构层放置到单独的包中的功能方法。在示例应用程序中,每个包通常只有几个或只有一个类。
这种初步方法很好,因为一个人为的例子通常用于:1)概念性地绘制出所呈现的框架的体系结构,2)以单一逻辑目的完成(例如添加/删除/更新/删除宠物诊所)。问题在于许多读者认为这是一个没有界限的标准。
随着“业务”应用程序的扩展以包含越来越多的功能,遵循 功能 方法会成为负担。虽然我知道在哪里寻找基于架构层的类型(例如“web”或“ui”包下的web控制器等),但是开发一个 逻辑 功能开始要求在许多包之间来回跳转。这至少是麻烦的,但比这更糟糕。
由于逻辑相关类型未打包在一起,因此过度公开了API;逻辑相关类型之间的交互被强制为“公共”,以便类型可以相互导入和交互(最小化默认/包可见性的能力会丢失)。
如果我正在构建框架库,那么我的软件包将遵循功能/架构打包方法。我的API消费者甚至可能会意识到他们的import语句包含以体系结构命名的直观包。
相反,在构建业务应用程序时,我将按功能打包。将Widget,WidgetService和WidgetController全部放在同一个“ com.myorg.widget。”包中然后利用默认可见性(并且具有更少的import语句和inter-package)我没有问题依赖关系)。
然而,有一些交叉案件。如果我的WidgetService被许多逻辑域(功能)使用,我可能会创建一个“ com.myorg.common.service。”包。我也很有可能创建可以跨功能重用的类,最后使用“ com.myorg.common.ui.helpers。”和“等软件包。 com.myorg.common.util。”。我甚至可能最终将所有这些后来的“常用”类移到一个单独的项目中,并将它们作为myorg-commons.jar依赖项包含在我的业务应用程序中。
答案 7 :(得分:2)
这取决于逻辑进程的粒度?
如果它们是独立的,您通常会在源代码管理中为它们创建一个新项目,而不是新的软件包。
我现在正在进行的项目是错误的逻辑拆分,有一个jython方面的包,一个规则引擎的包,foo,bar,binglewozzle等的包。我正在寻找拥有该包中每个模块的特定于XML的解析器/编写器,而不是具有XML包(我之前已经完成),尽管仍然存在共享逻辑的核心XML包。然而,其中一个原因是它可能是可扩展的(插件),因此每个插件也需要定义其XML(或数据库等)代码,因此集中这可能会在以后引入问题。
最后,似乎对特定项目来说似乎最合理。我认为按照典型项目分层图的方式打包很容易。你最终会得到逻辑和功能包装的混合。
标记的命名空间需要什么。某些Jython功能的XML解析器可以标记为Jython和XML,而不必选择其中一个。
或许我正在唠叨。
答案 8 :(得分:1)
我个人会去寻找功能命名。简短的原因:它避免了代码重复或依赖性噩梦。
让我详细说明一下。当您使用具有自己的包树的外部jar文件时会发生什么?您正在有效地将(已编译的)代码导入到项目中,并使用它(功能上分离的)包树。是否有意义同时使用这两个命名约定?不,除非那是对你隐藏的。如果您的项目足够小并且只有一个组件,那就是它。但是如果你有几个逻辑单元,你可能不想重新实现,比方说,数据文件加载模块。您希望在逻辑单元之间共享它,而不是在逻辑上不相关的单元之间存在人为依赖关系,而不必选择将该特定共享工具放入哪个单元。
我想这就是为什么功能命名在达到或意图达到一定大小的项目中使用最多的原因,并且在类命名约定中使用逻辑命名来跟踪特定角色(如果有的话)包中的类。
我将尝试更准确地回应您在逻辑命名方面的每一点。
如果您在更改计划时必须在旧类中钓鱼来修改功能,那么这是抽象不良的标志:您应该构建提供定义明确的功能的类,可以在一个简短的句子中定义。只有少数顶级课程应该汇集所有这些以反映您的商业智能。这样,您将能够重用更多代码,更容易维护,更清晰的文档和更少的依赖性问题。
这主要取决于你弄清楚你的项目的方式。当然,逻辑和功能视图是正交的。因此,如果您使用一个命名约定,则需要将另一个应用于类名以保持一些顺序,或者从一个命名约定到另一个命名约定。
访问修饰符是允许其他理解处理的类访问类内部的好方法。逻辑关系并不意味着理解算法或并发约束。功能可能,虽然它没有。我非常厌倦除了公共和私有之外的访问修饰符,因为它们经常隐藏缺乏适当的架构和类抽象。
在大型商业项目中,不断变化的技术比你想象的更频繁。例如,我不得不改变3次XML解析器,2次缓存技术和2次地理定位软件。好事我把所有细节都隐藏在一个专门的包装中......
答案 9 :(得分:1)
我尝试以这样的方式设计包结构:如果我要绘制依赖图,那么很容易遵循并使用一致的模式,尽可能少的循环引用。
对我来说,这在垂直命名系统中更容易维护和可视化,而不是水平。如果component1.display具有对component2.dataaccess的引用,那么抛出的警告数量会比display.component1引用dataaccess时引发更多警告。 COMPONENT2。
当然,两者共享的组件都在自己的包中。
答案 10 :(得分:1)
我完全遵循并提出逻辑(“按功能”)组织!包应尽可能地遵循“模块”的概念。功能组织可以在项目上扩展模块,从而减少封装,并且易于实现细节的变化。
让我们以Eclipse插件为例:将所有视图或操作放在一个包中会很麻烦。相反,功能的每个组件都应该转到功能包中,或者如果有很多,则进入子包(featureA.handlers,featureA.preferences等)。
当然,问题在于分层包系统(其中包括Java),这使得正交问题的处理变得不可能或者至少非常困难 - 尽管它们无处不在!
答案 11 :(得分:0)
从纯粹实用的角度来看,java的可见性构造允许同一个包中的类访问具有protected
和default
可见性的方法和属性,以及public
个。从完全不同的代码层使用非公共方法肯定会是一个很大的代码味道。所以我倾向于将来自同一层的类放入同一个包中。
我不经常在其他地方使用这些受保护或默认方法 - 除了可能在类的单元测试中 - 但是当我这样做时,总是来自同一层的类
答案 12 :(得分:0)
这取决于。在我的工作中,我们有时会按功能(数据访问,分析)或资产类别(信用,股票,利率)拆分包。只需选择最适合您团队的结构。
答案 13 :(得分:0)
这是一个有趣的实验,根本不使用包(根包除外)。
然后出现的问题是,何时以及为什么引入包有意义。据推测,答案将与您在项目开始时所回答的内容不同。
我认为你的问题一直存在,因为包类似于类别,有时候很难决定其中一个。有时标签会更加欣赏,以便在很多情况下可以使用类。
答案 14 :(得分:-4)
根据我的经验,可重用性产生的问题多于解决问题。随着最新的&amp;廉价的处理器和内存,我宁愿重复代码而不是紧密集成以便重用。