我很难理解 UML 中组合和聚合之间的区别。有人可以给我一个很好的比较和对比吗?我也很想学习识别它们在代码中的区别和/或查看一个简短的软件/代码示例。
编辑:我问的部分原因是因为我们在工作中正在进行反向文档活动。我们已经编写了代码,但我们需要返回并为代码创建类图。我们只想正确捕获关联。
根据经验:
class Person {
private Heart heart;
private List<Hand> hands;
}
class City {
private List<Tree> trees;
private List<Car> cars
}
在组合(Person、Heart、Hand)中,“子对象”(Heart、Hand)会在 Person 被销毁后立即销毁。
在聚合(City, Tree, Car) 中,“子对象” (Tree, Car) 在 City 被销毁时不会被销毁。
底线是,组合强调相互存在,而在聚合中,这个属性不是必需的。
聚合和组合之间的区别取决于上下文。
以另一个答案中提到的汽车为例 - 是的,汽车尾气确实可以“独立”站立,因此可能不会与汽车组成 - 但这取决于应用程序。如果您构建的应用程序实际上必须处理独立的汽车尾气(汽车店管理应用程序?),聚合将是您的选择。但如果这是一个简单的赛车游戏,而汽车尾气只是作为汽车的一部分——那么,构图就很好了。
棋盘?同样的问题。仅在某些应用程序中,没有棋盘就不存在棋子。在其他(如玩具制造商)中,棋子肯定不能组成棋盘。
当尝试将组合/聚合映射到您最喜欢的编程语言时,情况会变得更糟。在某些语言中,差异可能更容易注意到(“按引用”与“按值”,当事情很简单时),但在其他语言中可能根本不存在。
最后一句忠告?不要在这个问题上浪费太多时间。这不值得。这种区别在实践中几乎没有用(即使您有一个完全清晰的“组合”,由于技术原因,您可能仍希望将其实现为聚合 - 例如,缓存)。
组合和聚合是关联的类型。它们密切相关,就编程而言,两者之间似乎没有太大区别。我将尝试通过java代码示例来解释这两者之间的区别
聚合:对象存在于另一个之外,在外部创建,因此它作为参数(例如)传递给构造函数。例如:人——汽车。汽车是在不同的环境中创建的,然后成为个人财产。
// code example for Aggregation:
// reference existing HttpListener and RequestProcessor
public class WebServer {
private HttpListener listener;
private RequestProcessor processor;
public WebServer(HttpListener listener, RequestProcessor processor) {
this.listener = listener;
this.processor = processor;
}
}
构图:对象仅作为其他对象的一部分存在或仅在其他对象内部才有意义。例如:人——心。你不会创造一颗心,然后将它传递给一个人。相反,心是在人被创造的时候被创造出来的。
// code example for composition:
// create own HttpListener and RequestProcessor
public class WebServer {
private HttpListener listener;
private RequestProcessor processor;
public WebServer() {
this.listener = new HttpListener(80);
this.processor = new RequestProcessor(“/www/root”);
}
}
这里用一个例子来解释聚合和组合之间的区别
组合意味着子对象与父对象共享一个生命周期。聚合没有。例如,棋盘是由棋盘组成的——没有棋盘,棋盘就不会真正存在。但是,汽车是零件的集合体——如果当时不是汽车的一部分,汽车尾气仍然是汽车尾气。
我学到的例子是手指到手。你的手是由手指组成的。它拥有它们。如果手死了,手指也会死。你不能“聚合”手指。你不能随便抓住多余的手指,随意将它们从你的手上取下来。
正如另一位海报所说,从设计的角度来看,这里的价值通常与对象的寿命有关。假设您有一个客户并且他们有一个帐户。该帐户是客户的“组合”对象(至少,在我能想到的大多数情况下)。如果您删除客户,则该帐户本身没有任何价值,因此它也会被删除。在创建对象时,通常情况正好相反。由于帐户仅在客户的上下文中有意义,因此您将创建帐户作为客户创建的一部分(或者,如果您懒惰地这样做,它将成为某些客户交易的一部分)。
在设计中考虑哪些对象拥有(组合)其他对象与仅引用(聚合)其他对象的对象是很有用的。它可以帮助确定对象创建/清理/更新的责任所在。
就代码而言,通常很难说清楚。代码中的大多数内容都是对象引用,因此引用的对象是组合(拥有)还是聚合可能并不明显。
令人惊讶的是,关于部分-整体关联概念聚合和组合之间的区别存在多少混淆。主要问题是普遍存在的误解(甚至在专业软件开发人员和 UML 的作者之间),即组合的概念意味着整体与其部分之间的生命周期依赖关系,因此如果没有整体,部分就无法存在。但这种观点忽略了这样一个事实,即在某些情况下,部分与不可共享的部分之间存在关联,其中部分可以从整体中分离出来,并在整体的破坏中幸存下来。
在 UML 规范文档中,术语“组合”的定义一直暗示着不可共享的部分,但一直不清楚“组合”的定义特征是什么,什么仅仅是可选特征。即使在新版本(截至 2015 年)UML 2.5 中,在尝试改进术语“组合”的定义后,它仍然模棱两可,并且没有提供任何指导如何对不可共享的部分-整体关联进行建模部分可以与整体分离并在整体破坏中幸存的部分,而不是部分不能分离并与整体一起被破坏的情况。他们说
如果删除了复合对象,则它的所有作为对象的零件实例都将随之删除。
但同时他们也说
部分对象可以在复合对象被删除之前从复合对象中移除,因此不会作为复合对象的一部分被删除。
这种混淆表明 UML 定义的不完整,它没有考虑组件和组合之间的生命周期依赖关系。因此,重要的是要了解如何通过为 << inseparable >> 组合引入 UML 原型来增强 UML 定义,其中组件不能从它们的组合中分离,因此,只要它们的组合被破坏,就必须被破坏。
正如Martin Fowler 所解释的,表征组合的主要问题是“一个对象只能是一个组合关系的一部分”。Geert Bellekens的优秀博文UML Composition vs Aggregation vs Association也解释了这一点。除了组合的这个定义特征(具有独占的或不可共享的部分)之外,组合还可能带有组合及其组件之间的生命周期依赖关系。事实上,这样的依赖有两种:
Person
,Heart
如下图所示。当它的主人去世时,心脏要么被摧毁,要么被移植给另一个人。Person
组合Brain
。总而言之,生命周期依赖只适用于特定的组合情况,但并不普遍,因此它们不是一个定义特征。
UML 规范声明:“可以在组合实例被删除之前从组合实例中移除一部分,因此不会作为组合实例的一部分被删除。” 在一个Car
-<code>Engine组合的例子中,如下图所示,很明显是在汽车被销毁之前,可以将引擎从汽车上分离出来的情况,在这种情况下,引擎没有被破坏,可以重新-用过的。这由合成线的复合侧的零或一个多重性暗示。
The multiplicity of a composition's association end at the composite side is either 1 or 0..1, depending on the fact if components have a mandatory composite (must be attached to a composite) or not. If components are inseparable, this implies that they have a mandatory composite.
An aggregation is another special form of association with the intended meaning of a part–whole relationship, where the parts of a whole can be shared with other wholes. For instance, we can model an aggregation between the classes DegreeProgram
and Course
, as shown in the following diagram, since a course is part of a degree program and a course can be shared among two or more degree programs (e.g. an engineering degree could share a C programming course with a computer science degree).
但是,具有可共享部分的聚合的概念实际上并没有多大意义,因此它对实现没有任何影响,因此许多开发人员不喜欢在他们的类图中使用白色菱形,而只是建模一个简单的关联反而。UML 规范说:“共享聚合的精确语义因应用领域和建模者而异”。
聚合在整体侧的关联端的多重性可以是任意数量(*),因为一部分可能属于任意数量的整体,或在它们之间共享。
用代码术语来说,组合通常表明包含对象负责创建组件*的实例,并且包含对象持有对它的唯一长期引用。因此,如果父对象被取消引用并被垃圾回收,那么子对象也将如此。
所以这段代码...
Class Order
private Collection<LineItem> items;
...
void addOrderLine(Item sku, int quantity){
items.add(new LineItem(sku, quantity));
}
}
表明 LineItem 是 Order 的一个组件 - LineItem 在其包含的订单之外不存在。但是 Item 对象不是按订单构造的——它们是根据需要传入的,并且会继续存在,即使商店没有订单。所以它们是关联的,而不是组件。
* nb 容器负责实例化组件,但它实际上可能不会调用 new...() 本身 - 这是 java,通常首先要经过一两个工厂!
其他答案中提供的概念插图很有用,但我想分享另一点我发现有帮助的观点。
对于代码生成、源代码或关系数据库的 DDL,我已经取得了一些进展。在那里,我使用组合来指示一个表在我的代码中具有一个不可为空的外键(在数据库中)和一个不可为空的“父”(通常是“最终”)对象。我使用聚合,我希望记录或对象能够作为“孤儿”存在,不附加到任何父对象,或者被不同的父对象“采用”。
换句话说,我使用组合表示法作为简写来暗示在为模型编写代码时可能需要的一些额外约束。
我喜欢的例子: 组成: 水是池塘的一部分。(池塘是水的组合物。) 聚合: 池塘有鸭和鱼(池塘聚合鸭和鱼)
如您所见,我将“part-of”和“has”加粗,因为这两个短语通常可以指出类之间存在什么样的联系。
但正如其他人所指出的,很多时候连接是组合还是聚合取决于应用程序。
很难区分聚合关系和复合关系,但我要举一些例子,我们有一个房子和房间,这里我们有一个复合关系,房间是房子的一部分,房间生活开始了with house life's and will finish when house life's done, 房间是房子的一部分,我们谈论构图,比如国家和首都,书籍和页面。以聚合关系为例,拿球队和球员来说,球员可以没有球队而存在,球队是一群球员,球员的生活可以在球队生活之前开始,如果我们谈到编程,我们可以创建球员,然后我们将创建球队,但是对于组合 no,我们在 house 内创建了 room s。合成---->合成|合成。聚合 --------> 组 | 元素
让我们设定条款。Aggregation 是 UML 标准中的一个元术语,表示组合和共享聚合,简称为shared。通常它被错误地命名为“聚合”。这很糟糕,因为组合也是一种聚合。据我了解,您的意思是“共享”。
进一步从 UML 标准:
复合 - 表示属性是复合聚合的,即复合对象负责组合对象(部分)的存在和存储。
因此,大学与大教堂协会是一个组合,因为大学不存在大教堂(恕我直言)
共享聚合的精确语义因应用领域和建模者而异。
即,如果您仅遵循您或其他人的某些原则,则所有其他关联都可以绘制为共享聚合。也看这里。
考虑人体部位,如肾脏、肝脏、大脑。如果我们尝试在这里映射组合和聚合的概念,它会像:
在肾、肝等身体部位移植出现之前,这两个身体部位是与人体组成的,不能与人体孤立存在。
但是在身体部位移植出现之后,它们可以移植到另一个人体中,因此这些部位与人体是聚合的,因为它们现在可以与人体隔离存在。