Java中的组合、聚合和关联关系

1.介绍

对象之间在现实生活和编程中都有关系。有时很难理解或实现这些关系。 在本教程中,我们将重点介绍 Java 的三种关系类型,这些关系有时很容易混合:组合、聚合和关联。

2.组合

组成是一种"belong-to"的关系类型。这意味着其中一个对象是逻辑上较大的结构,其中包含另一个对象。
换句话说,它是其他对象的一部分或成员。 或者,我们通常称之为"has-a"关系(而不是"is-a"关系,即继承关系)。
例如,房间属于建筑物,换句话说,建筑物里有一个房间。因此,基本上,无论是称之为"belong-to"还是"has-a"只是一个观点的问题。
组合是一种强烈的"has-a"关系,因为包含对象拥有它。因此,对象的生命周期是绑定的。
这意味着,如果我们销毁所有者对象,其成员也将销毁它。例如,在上一个示例中,房间会因建筑物而被摧毁。
请注意,这并不意味着,没有其任何部分,包含对象就无法存在。例如,我们可以拆掉建筑物内的所有墙壁,
从而摧毁房间。但是这栋建筑仍然存在。 就基数而言,容器可以具有我们想要的尽可能多的部件。
但是,所有部件都需要有一个容器。

2.1 UML

在 UML 中,我们使用以下符号表示组合:

请注意,菱形位于包含对象处,是线的基础,而不是箭头。为了清楚起见,我们经常也画箭头:

因此,我们可以将此 UML 构造用于Building-Room的示例:

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.聚合

聚合也是一种"has-a"关系。它与组合的区别是:它不涉及拥有。
因此,对象的生命周期没有绑定:每个对象的生命周期可以彼此独立存在。
例如,汽车及其车轮。我们可以摘下轮子,它们仍然存在。我们可以安装其他(预先存在的)车轮,
或安装这些到另一辆车,一切都会工作得很好。 当然,没有轮子或分离的车轮的汽车不会像有轮子的汽车那样有用。
但这就是为什么这种关系首先存在:组装零件到一个更大的结构,这是能够比它的零件更多的东西。
由于聚合不涉及拥有,因此成员不需要仅绑定到一个容器。例如,三角形由线段组成。但三角形可以共享段作为它们的边。

3.1 UML

聚合与组合非常相似。唯一的逻辑区别是聚合是较弱的关系。 因此,UML 表示也非常相似。唯一的区别是菱形块是空的:

对于汽车和车轮,那么,我们会做:

3.2 源码

Java 中,我们可以使用普通旧引用对聚合建模:

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

成员可以是任何类型的类,非静态内部类除外。

在上面的代码段中,两个类都有各自的源文件。但是,我们也可以使用静态内部类:

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

请注意,Java 将仅在非静态内部类中创建隐式引用。因此,我们必须在需要的地方手动维护关系:

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

4.关联

关联是三者之间最弱的关系。它不是"has-a"关系,没有一个对象是另一个对象的部分或成员。

关联仅意味着对象"知道"彼此。例如,母亲和她的孩子。

4.1 UML

UML 中,我们可以使用箭头标记关联:

如果关联是双向的,我们可以使用两个箭头,一个两端带有箭头的箭头,或者一条没有任何箭头的线:

我们可以在 UML 中代表母亲和她的孩子,然后:

源码

Java中,我们可以像对聚合一样对关联建模:

class Child {}
 
class Mother {
    List<Child> children;
}

但是,等等,我们如何判断引用是否意味着聚合或关联?
嗯,我们不能。只有逻辑上的区别:其中一个对象是否是另一个对象的一部分。

此外,我们必须在两端手动维护引用,就像使用聚合一样:

class Child {
    Mother mother;
}
 
class Mother {
    List<Child> children;
}

5.UML 侧记

为了清楚起见,有时我们希望在 UML 关系图上定义关系的基数。
我们可以通过将其写入箭头的末端来执行此操作:

请注意,将零写成基数没有意义,因为它表示没有关系。唯一的例外是,当我们想要使用范围来指示可选关系时:

另请注意,由于在组合中正好有一个所有者,所以我们在图表上没有指明它。

6.一个复杂的例子

让我们看一个(小)更复杂的例子!我们将为一所具有多个院系的大学建模。教授们在各个院系工作,他们互相也是朋友。
我们关闭大学后,这些院系会存在吗?当然不是,所以这是一个组合。 但是教授们仍然存在(希望如此)。我们必须决定哪个更合乎逻辑:
我们是否把教授当作院系的一部分。或者:他们是否是部门的成员?是的,是的。因此,它是一个聚合。除此之外,教授可以在多个部门工作。
教授之间的关系是关联性的,因为说教授是另一个教授的一部分是毫无意义的。 因此,我们可以使用以下 UML 关系图对此示例进行建模:

代码如下所示:

class University {
    List<Department> department;   
}
 
class Department {
    List<Professor> professors;
}
 
class Professor {
    List<Department> department;
    List<Professor> friends;
}

请注意,如果我们依赖于术语"has-a"、"belong-to"、"member-of"和"part-of"等,我们可以很容易地识别对象之间的关系。

7.结论

在本文中,我们看到了组合、聚合和关联的属性和表示形式。我们还在 UMLJava 中看到了如何对这些关系建模。

与往常一样,这些示例可在 GitHub 上找到。

原文链接::https://www.baeldung.com/java-composition-aggregation-association
作 者:Attila Fejér
译 者:lee

你可能感兴趣的:(JAVA)