Samenstelling, aggregatie en associatie in Java

1. Inleiding

Objecten hebben onderlinge relaties, zowel in het echte leven als tijdens het programmeren. Soms is het moeilijk om deze relaties te begrijpen of te implementeren.

In deze tutorial zullen we ons concentreren op Java's kijk op drie soms gemakkelijk door elkaar te halen soorten relaties: compositie, aggregatie en associatie.

2. Samenstelling

Samenstelling is een relatie van het type "behoort tot". Het betekent dat een van de objecten een logisch grotere structuur is, die het andere object bevat. Met andere woorden, het is een onderdeel of lid van het andere object.

Alternatief, we noemen het vaak een "heeft-een" -relatie (in tegenstelling tot een "is-a" -relatie, wat erfenis is).

Een kamer hoort bijvoorbeeld bij een gebouw, of met andere woorden: een gebouw heeft een kamer. Dus eigenlijk is het slechts een kwestie van gezichtspunt of we het ‘toebehoort aan’ of ‘has-a’ noemen.

Compositie is een sterke soort "heeft-een" -relatie omdat het bevattende object de eigenaar is. Daarom de levenscycli van de objecten zijn aan elkaar gebonden. Het betekent dat als we het eigenaarobject vernietigen, de leden er ook mee vernietigd zullen worden. De kamer wordt bijvoorbeeld vernietigd met het gebouw in ons vorige voorbeeld.

Merk op dat dit niet betekent dat het bevattende object niet kan bestaan ​​zonder een van zijn onderdelen. We kunnen bijvoorbeeld alle muren in een gebouw afbreken en zo de kamers vernietigen. Maar het gebouw blijft bestaan.

In termen van kardinaliteit kan een bevattend object zoveel onderdelen hebben als we willen. Echter, alle onderdelen moeten precies één container hebben.

2.1. UML

In UML duiden we compositie aan met het volgende symbool:

Merk op dat de diamant zich bij het bevattende object bevindt en de basis van de lijn is, niet een pijlpunt. Voor de duidelijkheid tekenen we ook vaak de pijlpunt:

Dus we kunnen deze UML-constructie gebruiken voor ons Building-Room-voorbeeld:

2.2. Broncode

In Java kunnen we dit modelleren met een niet-statische innerlijke klasse:

klasgebouw {klaslokaal {}}

Als alternatief kunnen we die klasse ook in de body van een methode declareren. Het maakt niet uit of het een klasse met naam, een anonieme klasse of een lambda is:

klasse Gebouw {Kamer createAnonymousRoom () {retourneer nieuwe kamer () {@Override void doInRoom () {}}; } Room createInlineRoom () {klasse InlineRoom implementeert Room {@Override void doInRoom () {}} retourneer nieuwe InlineRoom (); } Kamer createLambdaRoom () {return () -> {}; } interface Room {void doInRoom (); }}

Merk op dat het essentieel is dat onze innerlijke klasse niet-statisch moet zijn, aangezien het al zijn instanties aan de bevattende klasse bindt.

Gewoonlijk wil het bevattende object toegang tot zijn leden. Daarom moeten we hun referenties opslaan:

klasse Gebouw {List rooms; klaslokaal {}}

Merk op dat alle inner class-objecten een impliciete verwijzing naar hun bevattende object opslaan. Daarom hoeven we het niet handmatig op te slaan om er toegang toe te krijgen:

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

3. Aggregatie

Aggregatie is ook een "heeft-een" -relatie. Wat het onderscheidt van compositie, is dat het niet gaat om bezit. Hierdoor zijn de levenscycli van de objecten niet gebonden: ze kunnen allemaal onafhankelijk van elkaar bestaan.

Bijvoorbeeld een auto en zijn wielen. We kunnen de wielen eraf halen, en ze zullen nog steeds bestaan. We kunnen andere (reeds bestaande) wielen monteren, of deze op een andere auto monteren en alles werkt prima.

Natuurlijk zal een auto zonder wielen of een losgemaakt wiel niet zo handig zijn als een auto met wielen erop. Maar daarom bestond deze relatie in de eerste plaats: to monteer de onderdelen tot een grotere constructie, die tot meer dingen in staat is dan de onderdelen.

Aangezien bij aggregatie geen eigendom betrokken is, een lid hoeft niet aan slechts één container te zijn gebonden. Een driehoek is bijvoorbeeld gemaakt van segmenten. Maar driehoeken kunnen segmenten delen als hun zijden.

3.1. UML

Aggregatie lijkt sterk op compositie. Het enige logische verschil is dat aggregatie een zwakkere relatie is.

Daarom lijken UML-weergaven ook erg op elkaar. Het enige verschil is dat de diamant leeg is:

Voor auto's en wielen zouden we dan doen:

3.2. Broncode

In Java kunnen we aggregatie modelleren met een gewone oude referentie:

klasse Wheel {} class Car {Lijst wielen; }

Het lid kan elk type klasse zijn, behalve een niet-statische innerlijke klasse.

In het bovenstaande codefragment hebben beide klassen hun eigen bronbestand. We kunnen echter ook een statische innerlijke klasse gebruiken:

klasse Auto {Lijst wielen; statische klasse Wheel {}}

Merk op dat Java alleen een impliciete verwijzing maakt in niet-statische innerlijke klassen. Daarom moeten we de relatie handmatig onderhouden waar we die nodig hebben:

klasse Wheel {Auto auto; } class Car {List wheels; }

4. Vereniging

Associatie is de zwakste relatie tussen de drie. Het is geen "heeft-een" -relatiezijn geen van de objecten onderdelen of leden van een ander.

Associatie betekent alleen dat de objecten elkaar 'kennen'. Bijvoorbeeld een moeder en haar kind.

4.1. UML

In UML kunnen we een associatie markeren met een pijl:

Als de associatie bidirectioneel is, kunnen we twee pijlen gebruiken, een pijl met een pijlpunt aan beide uiteinden of een lijn zonder pijlpunten:

We kunnen een moeder en haar kind vertegenwoordigen in UML, dan:

4.2. Broncode

In Java kunnen we associatie op dezelfde manier modelleren als aggregatie:

klas Kind {} klas Moeder {Lijst kinderen; }

Maar wacht, hoe kunnen we zien of een referentie aggregatie of associatie betekent?

Nou, dat kunnen we niet. Het verschil is niet meer dan logisch: of het ene object deel uitmaakt van het andere of niet.

We moeten de referenties ook aan beide kanten handmatig onderhouden, zoals we deden met aggregatie:

klasse Kind {Moeder moeder; } class Moeder {Lijst kinderen; }

5. UML Sidenote

Omwille van de duidelijkheid willen we soms de kardinaliteit van een relatie op een UML-diagram definiëren. We kunnen dit doen door het naar de uiteinden van de pijl te schrijven:

Merk op dat het niet logisch is om nul als kardinaliteit te schrijven, omdat het betekent dat er geen relatie is. De enige uitzondering is wanneer we een bereik willen gebruiken om een ​​optionele relatie aan te geven:

Merk ook op dat aangezien er in de compositie precies één eigenaar is, we dit niet aangeven op de diagrammen.

6. Een complex voorbeeld

Laten we een (klein) complexer voorbeeld bekijken!

We modelleren een universiteit met afdelingen. Op elke afdeling werken hoogleraren, die ook onderling vrienden hebben.

Zullen de afdelingen bestaan ​​nadat we de universiteit hebben gesloten? Natuurlijk niet, daarom is het een compositie.

Maar de professoren zullen (hopelijk) nog steeds bestaan. We moeten beslissen wat logischer is: of we hoogleraren beschouwen als onderdelen van de afdelingen of niet. Of: zijn ze lid van de afdelingen of niet? Ja, dat zijn ze. Daarom is het een samenvoeging. Bovendien kan een hoogleraar op meerdere afdelingen werken.

De relatie tussen hoogleraren is associatie omdat het geen zin heeft om te zeggen dat een hoogleraar deel uitmaakt van een ander.

Als resultaat kunnen we dit voorbeeld modelleren met het volgende UML-diagram:

En de Java-code ziet er als volgt uit:

klasse Universiteit {lijst afdeling; } klasse Afdeling {Lijst professoren; } class Professor {List department; Lijst met vrienden; }

Merk op dat als we vertrouw op de termen 'heeft-a', 'behoort tot', 'lid van', 'deel van', enzovoort, we kunnen gemakkelijker de relaties tussen onze objecten identificeren.

7. Conclusie

In dit artikel hebben we de eigenschappen en weergave van compositie, aggregatie en associatie gezien. We hebben ook gezien hoe we die relaties kunnen modelleren in UML en Java.

Zoals gewoonlijk zijn de voorbeelden beschikbaar op GitHub.