1. 引言

在现实世界和编程中,对象之间存在各种关系。理解这些关系有时候并不容易,尤其是组合(Composition)、聚合(Aggregation)和关联(Association)这三者之间的区别。

本文将深入讲解这三种常见的对象关系,帮助你准确识别并正确使用它们。


2. 组合(Composition)

组合是一种“属于”关系,也常被称为“has-a”关系(与“is-a”关系即继承相对)。

组合是一种强“has-a”关系。一个对象拥有另一个对象,生命周期绑定在一起。如果拥有者被销毁,其组成部分也将被销毁。

例如,一个房间属于一栋建筑。如果建筑被拆除,房间自然也不复存在。

2.1 UML 表示

在 UML 中,组合用实心菱形箭头表示:

composition

更清晰的带箭头版本:

composition arrow

Building-Room 示例:

composition example

2.2 示例代码

在 Java 中,组合通常通过非静态内部类实现:

class Building {
    class Room {}
}

也可以使用匿名类、局部类或 Lambda:

class Building {
    Room createAnonymousRoom() {
        return new Room() {
            @Override
            void doInRoom() {}
        };
    }

    Room createInlineRoom() {
        class InlineRoom implements Room {
            @Override
            void doInRoom() {}
        }
        return new InlineRoom();
    }
    
    Room createLambdaRoom() {
        return () -> {};
    }

    interface Room {
        void doInRoom();
    }
}

通常我们会存储对象引用:

class Building {
    List<Room> rooms;
    class Room {}
}

内部类隐式持有外部类的引用:

class Building {
    String address;
    
    class Room {
        String getBuildingAddress() {
            return Building.this.address;
        }   
    }   
}

3. 聚合(Aggregation)

聚合也是一种“has-a”关系,但与组合不同,它不涉及拥有权。对象之间生命周期独立。

例如,汽车和轮胎的关系。轮胎可以被拆下,安装到其他车上,仍然有效。

3.1 UML 表示

聚合用空心菱形表示:

aggregation

汽车与轮胎示例:

aggregation example

3.2 示例代码

聚合通常通过普通引用实现:

class Wheel {}

class Car {
    List<Wheel> wheels;
}

也可以使用静态内部类:

class Car {
    List<Wheel> wheels;
    static class Wheel {}
}

如果需要双向引用,需手动维护:

class Wheel {
    Car car;
}

class Car {
    List<Wheel> wheels;
}

4. 关联(Association)

关联是三者中最弱的关系。它不是“has-a”关系,而是对象之间相互“知道”对方的存在。

例如,母亲和孩子之间的关系。

4.1 UML 表示

单向关联用箭头表示:

association

双向关联可以用双箭头或无箭头线:

association-bidirectional

母亲与孩子示例:

association example

4.2 示例代码

关联与聚合类似,通过引用实现:

class Child {}

class Mother {
    List<Child> children;
}

如果是双向关联,需要手动维护双方引用:

class Child {
    Mother mother;
}

class Mother {
    List<Child> children;
}

5. UML 补充说明

有时我们会在 UML 图中表示关系的“基数”(cardinality)来增强可读性:

cardinality 1

0 通常不用于表示关系,除非用范围表示可选关系:

cardinality 2

组合关系中因为只有一个拥有者,所以通常不标注。


6. 综合示例

假设我们要建模一所大学,包含院系、教授,教授之间还有朋友关系。

  • 大学关闭后,院系不存在 → 组合
  • 教授离开大学后仍存在,且可能属于多个院系 → 聚合
  • 教授之间是朋友 → 关联

对应的 UML 图:

complex example

Java 实现如下:

class University {
    List<Department> departments;   
}

class Department {
    List<Professor> professors;
}

class Professor {
    List<Department> departments;
    List<Professor> friends;
}

小技巧: 使用“has-a”、“belongs-to”、“member-of”等术语有助于快速识别对象之间的关系。


7. 总结

组合、聚合和关联是面向对象设计中三种基本的关系:

类型 生命周期绑定 拥有关系 UML 表示
组合 实心菱形
聚合 空心菱形
关联 箭头或无箭头线

理解这些关系有助于我们设计出结构清晰、易于维护的系统。在实际编码中,注意使用合适的类结构和引用方式来准确表达对象之间的关系。

完整示例代码可在 GitHub 获取。


原始标题:Composition, Aggregation, and Association in Java | Baeldung