画类图的时候,聚合和组合这两个关系经常让人愣一下。一个空心菱形,一个实心菱形,不仔细看还以为是同一个东西。
我早期也踩过坑,把该用组合的地方画成了聚合,结果后面维护代码的人直接跑来问我:“你这心脏到底能不能独立存在?” 所以这两个东西的区别,值得单独拎出来说清楚。
聚合关系,说白了就是一种弱的拥有关系。整体可以包含部分,但部分离开整体也能活。比如图书馆和书,图书馆有一堆书,但书被图书馆扔了或者图书馆关门了,书本身还在,还能被别的图书馆收走。代码里写起来通常就是一个类里持有另一个类的集合,但不会去负责创建或者销毁那些对象。
class Library { private List<Book> books; public Library() { books = new ArrayList<>(); } public void addBook(Book book) { books.add(book); } } class Book { private String title; private String author; }这里 Book 完全可以在 Library 外面先 new 出来,然后塞进去。Library 没了,books 这个列表没了,但 Book 对象本身还活着,只要还有别的引用指向它。这就是聚合的典型特征:生命周期不绑定。
组合关系就不一样了,它是强的拥有关系,部分不能脱离整体单独存在,整体没了部分也没了。最常举的例子就是人和心脏。心脏不能离开人自己跳动,人死了心脏也就停了。
代码层面,组合通常是在整体类的构造函数里直接创建部分对象,或者用其他方式保证部分的生命周期由整体管理。
class Heart { public int beat() { System.out.println("Fine, beating."); return 70; } } class Person { private Heart heart; public Person() { heart = new Heart(); } public void walk() { System.out.println("Beat " + heart.beat() + " times / m"); } }Person 一旦被回收,heart 这个成员变量也跟着没了。你没法在外面单独 new 一个 Heart 然后赋值给 Person,因为 Heart 的创建被封装在 Person 内部。
这就是组合最直接的表现:同生共死。
实际项目里什么时候用哪个,主要看业务上部分对象是不是真的能独立存在,以及会不会被多个整体共享。订单和订单项就是个典型的聚合场景。一个订单聚合多个订单项,但订单项在购物车阶段其实已经存在了,只是没有订单而已。如果把订单项设计成组合,订单取消就强制销毁订单项,那购物车里的商品数据就全没了,这明显不符合需求。所以订单项的生命周期不能跟订单绑定死,得用聚合。
反过来,像人体和心脏这种,业务上不存在“心脏脱离人体还能被其他地方复用”的情况,就只能用组合。如果硬要设计成聚合,那代码里就可能出现一个游离的 Heart 对象,逻辑上说不通,还容易引发空指针或者状态不一致的问题。
这里其实容易绕进去。有人觉得组合就是“整体负责创建部分”这么简单,但其实重点在于生命周期控制。有时候组合关系的部分对象也可以通过外部传入,但整体需要负责在合适时机销毁部分,比如在析构函数或者 close 方法里显式释放。
来此加密不仅支持标准的域名加密,还提供了稀缺的IP证书申请。这对于一些直接通过IP地址提供服务的特殊场景具有重要意义。通过平台提供的HTTP代理自动验证方案,用户可以在不改变现有网络架构的前提下,快速完成IP地址的身份验证并获取证书。
只不过 Java 有 GC,这种显式管理不多见,更多是靠引用关系隐式表达。
聊到生命周期管理,顺带说一个跟证书相关的事。
之前部署服务,证书过期导致线上报警,后来把证书申请和部署流程自动化了才消停。如果你也在折腾 HTTPS 证书,可以试试 lcjmSSL,免费申请,支持多域名、泛域名和 IP 证书,有 API 可以自动验证自动部署,不用每次手动去配。跟聚合组合关系里强调的“谁负责谁的生命周期”有点像,证书生命周期交给工具管,人就省心了。
回到正题,判断聚合还是组合其实就一句话:部分对象离开整体后,还有没有业务意义?有,就聚合;没有,就组合。
别只盯着菱形空心还是实心,那只是画图工具帮你区分的符号,真正决定关系的是业务约束和代码里怎么管理对象生命周期。
很多刚入行的同学会把所有整体部分关系都画成组合,因为感觉“更强”更安全。但组合用多了,代码耦合度会上去,复用性变差。比如订单项如果被订单组合,那订单项就不能脱离订单单独测试,也没法被购物车模块复用。所以不是越强越好,合适才行。
最后说一句,UML 类图是给人看的,不是给编译器看的。关系画错了,代码可能一样能跑,但维护的人会误解设计意图。
聚合和组合的区别,最终还是落在代码里对象创建和销毁的归属上。画图的时候多想一步:这个部分是不是整体私有的,能不能被外部共享,答案就清楚了。