我很难理解UML中组合和聚合之间的区别。有人可以给我一个很好的比较和对比吗?我也很想学会在代码中识别它们之间的区别和/或看一个简短的软件/代码示例。
编辑:我问的部分原因是因为我们正在进行的反向文档活动。我们编写了代码,但是我们需要返回并为代码创建类图。我们只是想正确地捕捉这些关联。
聚合和组合之间的区别取决于上下文。
以另一个答案中提到的汽车为例 - 是的,确实汽车尾气可以“独立”,因此可能不会与汽车组成 - 但这取决于应用。如果您构建的应用程序实际上必须处理独立的汽车尾气(汽车商店管理应用程序?),那么聚合将是您的选择。但如果这是一个简单的赛车游戏,汽车尾气只能作为汽车的一部分 - 好吧,组成会很好。
国际象棋棋盘?同样的问题。只有在某些应用程序中,没有棋盘就不存在国际象棋棋子。在其他人(如玩具制造商的那些)中,棋子肯定不能组成棋盘。
在尝试将合成/聚合映射到您喜欢的编程语言时,情况变得更糟。在某些语言中,差异可能更容易被注意到(“通过引用”与“按价值”,当事情很简单时),但在其他语言中可能根本不存在。
还有最后一条忠告?不要在这个问题上浪费太多时间。这不值得。这种区别在实践中几乎没有用(即使你有一个完全清晰的“组合”,你可能仍然希望将其作为聚合实现,因为技术原因 - 例如,缓存)。
在聚合关系和复合关系之间做差异是如此困难,但我会举一些例子,我们有房子和房间,这里我们有复合关系,房间是房子的一部分,房间生活开始了当房子生活结束时房间生活和意志完成,房间是房子的一部分,我们谈论组成,如国家和资本,书籍和页面。对于聚合关系的例子,采取团队和球员,球员可以在没有球队的情况下存在,球队是一组球员,球员生活可以在球队生活之前开始,如果我们谈论编程,我们可以创建球员,在我们创建球队之后,但是对于作品no,我们在屋内创造房间。构成---->复合|组合。聚合------->组|元件
我们设定条款。 Aggregation是UML标准中的元项,意味着BOTH组合和共享聚合,简称为shared。通常它被错误地命名为“聚合”。它是坏的,因为组合也是聚合。据我了解,你的意思是“共享”。
进一步来自UML标准:
composite - 表示属性是复合聚合的,即复合对象负责组合对象(部件)的存在和存储。
所以,大学到cathedras协会是一个组成,因为大学不存在教学大师(恕我直言)
共享聚合的精确语义因应用领域和建模者而异。
即,如果您只遵循您或其他人的某些原则,则可以将所有其他关联绘制为共享聚合。还看here。
考虑人体部位,如肾脏,肝脏,大脑。如果我们尝试在这里映射组合和聚合的概念,它将是:
在肾脏和肝脏等身体部位移植出现之前,这两个身体部位与人体组成,不能与人体隔离。
但是在身体部分移植出现后,它们可以移植到另一个人体内,因此这些部分与人体聚集在一起,因为它们与人体隔离存在是可能的。
根据经验:
class Person {
private Heart heart;
private List<Hand> hands;
}
class City {
private List<Tree> trees;
private List<Car> cars
}
在构图(人物,心脏,手)中,一旦人物被摧毁,“子物体”(心脏,手)将被销毁。
在聚合(城市,树,汽车)中,当城市被摧毁时,“子对象”(树,汽车)将不会被销毁。
底线是,组合强调相互存在,而在聚合中,这个属性不是必需的。
组合和聚合是关联的类型。它们密切相关,在编程方面没有太大的区别。我将尝试通过java代码示例解释这两者之间的区别
聚合:对象存在于另一个之外,在外部创建,因此它作为参数(例如)传递给construtor。例如:人 - 车。汽车是在不同的环境中创造的,然后成为人的财产。
// 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定义的不完整性,该定义不考虑组件和组合之间的生命周期依赖性。因此,了解如何通过为“不可分离的”组合引入UML构造型来增强UML定义是很重要的,其中组件不能从它们的组合中分离,因此,每当它们的组合被销毁时都必须被销毁。
作为Martin Fowler has explained,表征构图的主要问题是“一个对象只能是一个构图关系的一部分”。 Geert Bellekens在优秀博客文章UML Composition vs Aggregation vs Association中也对此进行了解释。除了组合物的这种定义特征(具有独占或不可共享的部分)之外,组合物还可以在复合物与其组分之间具有生命周期依赖性。实际上,有两种这样的依赖:
Person
and Heart
之间的组合来举例说明,如下图所示。当一个人的主人已经死亡时,心脏会被摧毁或移植到另一个人身上。Person
and Brain
之间的组合物。总之,生命周期依赖性仅适用于特定的组合情况,但一般而言,它们不是一个定义特征。
UML规范声明:“在删除复合实例之前,可以从复合实例中删除部件,因此不会将其作为复合实例的一部分删除。”在Car
-Engine
组合的示例中,如下图所示,显然情况是在汽车被摧毁之前发动机可以从汽车上拆下,在这种情况下发动机不会被破坏并且可以重复使用。这由组合线的复合面上的零或一个多重性暗示。
复合边的组合物关联结束的多重性是1或0..1,这取决于组件是否具有强制复合(必须连接到复合材料)的事实。如果组件是不可分割的,这意味着它们具有强制性组合。
聚合是与部分 - 整体关系的预期含义相关联的另一种特殊形式,其中整体的各部分可以与其他整体共享。例如,我们可以对类DegreeProgram
和Course
之间的聚合建模,如下图所示,因为课程是学位课程的一部分,课程可以在两个或更多学位课程之间共享(例如工程学位可以共享具有计算机科学学位的C程序课程)。
然而,具有可共享部分的聚合的概念并不意味着真的,因此它对实现没有任何影响,因此许多开发人员不希望在他们的类图中使用白色菱形,而只是模拟一个简单的关联代替。 UML规范说:“共享聚合的精确语义因应用领域和建模者而异”。
整个侧面的聚合关联结束的多重性可以是任何数字(*),因为部分可以属于任何数量的整体,或者在任何数量的整体之间共享。
在代码术语中,组合通常建议包含对象负责创建组件*的实例,并且包含对象保存对它的唯一长期引用。因此,如果父对象被取消引用并被垃圾收集,那么孩子也将如此。
所以这段代码......
Class Order
private Collection<LineItem> items;
...
void addOrderLine(Item sku, int quantity){
items.add(new LineItem(sku, quantity));
}
}
表明LineItem是Order的一个组件 - LineItems在其包含顺序之外不存在。但Item对象不是按顺序构建的 - 它们是根据需要传递的,并且继续存在,即使商店没有订单。所以它们是关联的,而不是组件。
* n.b.容器负责实例化组件,但它实际上可能不会调用new ...()本身 - 这是java,通常首先要经过一两个工厂!
其他答案中提供的概念性插图很有用,但我想分享另一个我觉得有用的观点。
我已经从UML中获得了一些代码生成,源代码或关系数据库的DDL。在那里,我使用组合来表示一个表在我的代码中有一个不可为空的外键(在数据库中),以及一个不可为空的“父”(通常是“最终”)对象。我使用聚合,我希望记录或对象能够作为“孤儿”存在,不附加到任何父对象,或被另一个父对象“采用”。
换句话说,我使用组合符号作为简写来暗示在为模型编写代码时可能需要的一些额外约束。
我喜欢的例子:成分:水是池塘的一部分。 (池塘是水的组成。)聚集:池塘有鸭子和鱼(池塘聚集鸭子和鱼)
正如你所看到的,我已经加粗了“part-of”和“has”,因为这两个短语通常可以指出类之间存在什么样的连接。
但正如其他人所指出的,很多时候连接是组合还是聚合取决于应用程序。