Einführung
Objekte haben Beziehungen untereinander, sowohl im echten Leben als auch in der Programmierung. Manchmal ist es schwierig, diese Beziehungen zu verstehen oder zu implementieren.
In diesem Tutorial konzentrieren wir uns auf drei manchmal leicht zu verwechselnde Arten von Beziehungen in Java: Komposition, Aggregation und Assoziation.
Komposition
Komposition ist eine Beziehung vom Typ „gehört zu“. Es 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 dazu nennen wir es oft eine „hat-ein“-Beziehung (im Gegensatz zu einer „ist-ein“-Beziehung, die Vererbung ist).
Zum Beispiel gehört ein Raum zu einem Gebäude, oder anders gesagt, ein Gebäude hat einen Raum. Ob wir es also „gehört-zu“ oder „hat-a“ nennen, ist im Grunde nur eine Frage des Standpunkts.
Die Komposition ist eine starke Art der „hat-a“-Beziehung, weil das enthaltende Objekt Eigentümer ist. Daher sind die Lebenszyklen der Objekte gebunden. Das bedeutet, dass, wenn wir das Eigentümerobjekt zerstören, auch seine Mitglieder mit zerstört werden. Zum Beispiel wird der Raum mit dem Gebäude in unserem vorherigen Beispiel zerstört.
Das bedeutet nicht, dass das enthaltende Objekt nicht ohne einen seiner Teile existieren kann. Wir können zum Beispiel alle Wände innerhalb eines Gebäudes einreißen, also die Räume zerstören. Aber das Gebäude wird immer 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 Container haben.
2.1. UML
In UML zeigen wir die Komposition mit folgendem Symbol an:
Beachten Sie, dass die Raute am enthaltenden Objekt die Basis der Linie ist, nicht eine Pfeilspitze. Der Übersichtlichkeit halber zeichnen wir die Pfeilspitze oft mit ein:
So können wir also dieses UML-Konstrukt 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 Methodenrumpf deklarieren. Dabei spielt es 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 unsere innere Klasse unbedingt nicht statisch sein sollte, 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<Room> rooms; class Room {} }
Beachten Sie, dass alle Objekte der inneren Klasse eine implizite Referenz auf ihr enthaltendes Objekt speichern. Daher müssen wir sie nicht manuell speichern, um auf sie zuzugreifen:
class Building { String address; class Room { String getBuildingAddress() { return Building.this.address; } } }
Aggregation
Aggregation ist ebenfalls eine „has-a“-Beziehung. Was sie von der Komposition unterscheidet, ist, dass sie kein Besitzen beinhaltet. Das hat zur Folge, dass die Lebenszyklen der Objekte nicht gebunden sind: Jedes von ihnen kann unabhängig von den anderen existieren.
Zum Beispiel: ein Auto und seine Räder. Wir können die Räder abmontieren, und sie existieren immer noch. Wir können andere (bereits vorhandene) Räder montieren oder diese an einem anderen Auto anbringen und alles wird gut funktionieren.
Natürlich wird ein Auto ohne Räder oder ein abgenommenes Rad nicht so nützlich sein wie ein Auto mit seinen Rädern. Aber deshalb gibt es diese Beziehung überhaupt erst: um die Teile zu einem größeren Konstrukt zusammenzusetzen, das mehr kann als seine Teile.
Da Aggregation kein Besitzen beinhaltet, muss ein Mitglied nicht an nur einen Container gebunden sein. Zum Beispiel besteht ein Dreieck aus Segmenten. Aber Dreiecke können Segmente als ihre Seiten teilen.
3.1. UML
Aggregation ist der Komposition sehr ähnlich. Der einzige logische Unterschied ist, dass Aggregation eine schwächere Beziehung ist.
Daher sind auch die UML-Darstellungen sehr ähnlich. Der einzige Unterschied ist, dass die Raute 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<Wheel> wheels;}
Das Mitglied kann ein beliebiger Typ von Klasse sein, außer einer nicht statischen inneren Klasse.
Im obigen Codeschnipsel haben beide Klassen ihre eigene Quelldatei. Wir können aber auch eine statische innere Klasse verwenden:
class Car { List<Wheel> wheels; static class Wheel {}}
Beachten Sie, dass Java nur in nicht-statischen inneren Klassen eine implizite Referenz erzeugt. Deshalb müssen wir die Beziehung dort, wo wir sie brauchen, manuell pflegen:
class Wheel { Car car;}class Car { List<Wheel> wheels;}
Assoziation
Die Assoziation ist die schwächste der drei Beziehungen. Es ist keine „hat-a“-Beziehung, keines der Objekte ist Teil oder Mitglied eines anderen.
Assoziation bedeutet nur, dass die Objekte einander „kennen“. Zum Beispiel, eine Mutter und ihr Kind.
4.1. UML
In UML können wir eine Assoziation mit einem Pfeil markieren:
Wenn die Assoziation bidirektional ist, können wir zwei Pfeile, einen Pfeil mit einer Pfeilspitze an beiden Enden oder eine Linie ohne Pfeilspitzen verwenden:
Wir können eine Mutter und ihr Kind in UML darstellen, dann:
4.2. Quellcode
In Java können wir die Assoziation auf die gleiche Weise modellieren wie die Aggregation:
class Child {}class Mother { List<Child> children;}
Aber Moment, wie können wir erkennen, ob eine Referenz eine Aggregation oder eine Assoziation bedeutet?
Tja, das können wir nicht. Der Unterschied ist nur logisch: ob eines der Objekte Teil des anderen ist oder nicht.
Außerdem müssen wir die Referenzen auf beiden Seiten manuell pflegen, wie wir es bei der Aggregation getan haben:
class Child { Mother mother;}class Mother { List<Child> children;}
UML Sidenote
Der Übersichtlichkeit halber wollen wir manchmal die Kardinalität einer Beziehung in einem UML-Diagramm definieren. Wir können dies tun, indem wir sie an die Enden des Pfeils schreiben:
Beachten Sie, dass es keinen Sinn macht, Null als Kardinalität zu schreiben, da dies bedeutet, dass es keine Beziehung gibt. Die einzige Ausnahme ist, wenn wir einen Bereich verwenden wollen, um eine optionale Beziehung anzugeben:
Beachten Sie auch, dass wir in den Diagrammen nicht angeben, dass es in der Zusammensetzung genau einen Eigentümer gibt.
Ein komplexes Beispiel
Lassen Sie uns ein (etwas) komplexeres Beispiel sehen!
Wir modellieren eine Universität, die ihre Abteilungen hat. In jeder Abteilung arbeiten Professoren, die auch untereinander Freunde haben.
Werden die Abteilungen existieren, nachdem wir die Universität geschlossen haben? Natürlich nicht, deshalb ist es eine Komposition.
Aber die Professoren wird es (hoffentlich) noch geben. Wir müssen uns entscheiden, was logischer ist: ob wir die Professoren als Teil der Fachbereiche betrachten oder nicht. Oder: Sind sie Mitglieder der Departements oder nicht? Ja, das sind sie. Daher ist es eine Aggregation. Hinzu kommt, dass ein Professor in mehreren Abteilungen arbeiten kann.
Die Beziehung zwischen Professoren ist eine Assoziation, weil es keinen Sinn macht zu sagen, dass ein Professor Teil einer anderen Abteilung ist.
Als Ergebnis können wir dieses Beispiel mit dem folgenden UML-Diagramm modellieren:
Und der Java-Code sieht wie folgt aus:
class University { List<Department> department; }class Department { List<Professor> professors;}class Professor { List<Department> department; List<Professor> friends;}
Hinweis: Wenn wir uns auf die Begriffe „has-a“, „belongs-to“, „member-of“, „part-of“ und so weiter stützen, können wir die Beziehungen zwischen unseren Objekten leichter identifizieren.
Fazit
In diesem Artikel haben wir die Eigenschaften und die Darstellung von Komposition, Aggregation und Assoziation gesehen. Wir haben auch gesehen, wie man diese Beziehungen in UML und Java modelliert.
Wie üblich sind die Beispiele auf GitHub verfügbar.
Starten Sie mit Spring 5 und Spring Boot 2, durch den Learn Spring Kurs:
>> CHECK OUT THE COURSE