Zusammensetzung, Aggregation und Assoziation in Java

1. Einleitung

Objekte haben Beziehungen zwischen ihnen, sowohl im realen Leben als auch in der Programmierung. Manchmal ist es schwierig, diese Beziehungen zu verstehen oder umzusetzen.

In diesem Tutorial konzentrieren wir uns auf Javas Sichtweise auf drei manchmal leicht zu verwechselnde Arten von Beziehungen: Zusammensetzung, Aggregation und Assoziation.

2. Zusammensetzung

Komposition ist eine Art von Beziehung, zu der man gehört. Dies bedeutet, dass eines der Objekte eine logisch größere Struktur ist, die das andere Objekt enthält. Mit anderen Worten, es ist Teil oder Mitglied des anderen Objekts.

Alternativ nennen wir es oft eine "hat-eine" Beziehung (im Gegensatz zu einer "ist-eine" Beziehung, die Vererbung ist).

Zum Beispiel gehört ein Raum zu einem Gebäude, oder mit anderen Worten, ein Gebäude hat einen Raum. Ob wir es also "gehört" oder "hat-a" nennen, ist im Grunde nur eine Frage der Sichtweise.

Komposition ist eine starke Art von "hat-eine" Beziehung, weil das enthaltende Objekt sie besitzt. Daher sind die Lebenszyklen der Objekte gebunden. Wenn wir das Eigentümerobjekt zerstören, werden auch seine Mitglieder damit zerstört. Zum Beispiel wird der Raum mit dem Gebäude in unserem vorherigen Beispiel zerstört.

Beachten Sie, dass dies nicht bedeutet, dass das enthaltende Objekt ohne seine Teile nicht existieren kann. Zum Beispiel können wir alle Wände in einem Gebäude abreißen und so die Räume zerstören. Aber das Gebäude wird noch existieren.

In Bezug auf die Kardinalität kann ein enthaltendes Objekt so viele Teile haben, wie wir wollen. Allerdings müssen alle Teile genau einen Behälter haben .

2.1. UML

In UML geben wir die Komposition mit dem folgenden Symbol an:

Beachten Sie, dass sich der Diamant am enthaltenen Objekt befindet und die Basis der Linie ist, keine Pfeilspitze. Aus Gründen der Klarheit zeichnen wir häufig auch die Pfeilspitze:

Wir können dieses UML-Konstrukt also für unser Building-Room-Beispiel verwenden:

2.2. Quellcode

In Java können wir dies mit einer nicht statischen inneren Klasse modellieren:

class Building { class Room {} }

Alternativ können wir diese Klasse auch in einem Methodenkörper deklarieren. Es spielt keine Rolle, ob es sich um eine benannte Klasse, eine anonyme Klasse oder ein Lambda handelt:

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(); } }

Beachten Sie, dass es wichtig ist, dass unsere innere Klasse nicht statisch ist, da sie alle ihre Instanzen an die enthaltende Klasse bindet.

Normalerweise möchte das enthaltende Objekt auf seine Mitglieder zugreifen. Daher sollten wir ihre Referenzen speichern:

class Building { List rooms; class Room {} }

Beachten Sie, dass alle Objekte der inneren Klasse einen impliziten Verweis auf das enthaltende Objekt speichern. Daher müssen wir es nicht manuell speichern, um darauf zuzugreifen:

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

3. Aggregation

Aggregation ist auch eine Beziehung, die man hat. Was es von der Komposition unterscheidet, ist, dass es nicht das Besitzen beinhaltet. Infolgedessen sind die Lebenszyklen der Objekte nicht gebunden: Jeder von ihnen kann unabhängig voneinander existieren.

Zum Beispiel ein Auto und seine Räder. Wir können die Räder abnehmen, und sie werden noch existieren. Wir können andere (bereits vorhandene) Räder montieren oder diese in ein anderes Auto einbauen, und alles wird gut funktionieren.

Natürlich ist ein Auto ohne Räder oder ein abgenommenes Rad nicht so nützlich wie ein Auto mit Rädern. Aber deshalb bestand diese Beziehung in erster Linie: die Teile zu einem größeren Konstrukt zusammenzusetzen, das mehr kann als seine Teile .

Da für die Aggregation kein Besitz erforderlich ist, muss ein Mitglied nicht nur an einen Container gebunden sein . Beispielsweise besteht ein Dreieck aus Segmenten. Dreiecke können jedoch Segmente als ihre Seiten teilen.

3.1. UML

Die Aggregation ist der Zusammensetzung sehr ähnlich. Der einzige logische Unterschied ist, dass die Aggregation eine schwächere Beziehung ist.

Daher sind auch UML-Darstellungen sehr ähnlich. Der einzige Unterschied ist, dass der Diamant leer ist:

Für Autos und Räder würden wir also tun:

3.2. Quellcode

In Java können wir die Aggregation mit einer einfachen alten Referenz modellieren:

class Wheel {} class Car { List wheels; }

The member can be any type of class, except a non-static inner class.

In the code snippet above both classes have their separate source file. However, we can also use a static inner class:

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

Note that Java will create an implicit reference only in non-static inner classes. Because of that, we have to maintain the relationship manually where we need it:

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

4. Association

Association is the weakest relationship between the three. It isn't a “has-a” relationship, none of the objects are parts or members of another.

Association only means that the objects “know” each other. For example, a mother and her child.

4.1. UML

In UML, we can mark an association with an arrow:

If the association is bidirectional, we can use two arrows, an arrow with an arrowhead on both ends, or a line without any arrowheads:

We can represent a mother and her child in UML, then:

4.2. Source Code

In Java, we can model association the same way as aggregation:

class Child {} class Mother { List children; }

But wait, how can we tell if a reference means aggregation or association?

Well, we can't. The difference is only logical: whether one of the objects is part of the other or not.

Also, we have to maintain the references manually on both ends as we did with aggregation:

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

5. UML Sidenote

For the sake of clarity, sometimes we want to define the cardinality of a relationship on a UML diagram. We can do this by writing it to the ends of the arrow:

Note, that it doesn't make sense to write zero as cardinality, because it means there's no relationship. The only exception is when we want to use a range to indicate an optional relationship:

Also note, that since in composition there's precisely one owner we don't indicate it on the diagrams.

6. A Complex Example

Let's see a (little) more complex example!

We'll model a university, which has its departments. Professors work in each department, who also has friends among each other.

Will the departments exist after we close the university? Of course not, therefore it's a composition.

But the professors will still exist (hopefully). We have to decide which is more logical: if we consider professors as parts of the departments or not. Alternatively: are they members of the departments or not? Yes, they are. Hence it's an aggregation. On top of that, a professor can work in multiple departments.

Die Beziehung zwischen Professoren ist Assoziation, weil es keinen Sinn macht zu sagen, dass ein Professor Teil eines anderen ist.

Daher können wir dieses Beispiel mit dem folgenden UML-Diagramm modellieren:

Und der Java-Code sieht so aus:

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

Beachten Sie, dass wir die Beziehungen zwischen unseren Objekten leichter identifizieren können , wenn wir uns auf die Begriffe "hat-ein", "gehört zu", "Mitglied von", "Teil von" usw. stützen .

7. Fazit

In diesem Artikel haben wir die Eigenschaften und die Darstellung von Zusammensetzung, Aggregation und Assoziation gesehen. Wir haben auch gesehen, wie diese Beziehungen in UML und Java modelliert werden.

Wie üblich sind die Beispiele auf GitHub verfügbar.