Compositie, aggregatie en associatie in Java

Inleiding

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

In deze tutorial richten we ons op Java’s kijk op drie soms gemakkelijk door elkaar te halen soorten relaties: composition, aggregation, en association.

Composition

Composition is een “belongs-to” type relatie. Het betekent dat een van de objecten een logischerwijs grotere structuur is, die het andere object bevat. Met andere woorden, het is een deel of een lid van het andere object.

Atern noemen we het vaak een “has-a” relatie (in tegenstelling tot een “is-a” relatie, die overerving is).

Bijv. een kamer behoort tot een gebouw, of met andere woorden een gebouw heeft een kamer. Dus of we het “behoort-tot” of “heeft-een” noemen, is slechts een kwestie van gezichtspunt.

Een samenstelling is een sterk soort “heeft-een” relatie, omdat het bevattende object er eigenaar van is. Daarom zijn de levenscycli van de objecten aan elkaar gebonden. Dit betekent dat als we het object van de eigenaar vernietigen, de leden van dat object ook zullen worden vernietigd. Bijvoorbeeld, de kamer wordt 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 slopen, en daarmee de kamers vernietigen. Maar het gebouw zal nog steeds bestaan.

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

2.1. UML

In UML geven we samenstelling aan met het volgende symbool:

Merk op, dat de ruit bij het bevattende object staat en de basis van de lijn is, en geen pijlpunt. Voor de duidelijkheid tekenen we de pijlpunt vaak ook:

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 binnenklasse:

class Building { class Room {} }

Als alternatief kunnen we die klasse ook in een method body declareren. Het maakt niet uit of het een named class, een anonymous class of een lambda is:

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

Merk op, dat het essentieel is, dat onze inner class niet statisch is, omdat het al zijn instanties bindt aan de bevattende class.

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

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

Merk op, dat alle binnenklasse-objecten een impliciete referentie naar hun bevattende object opslaan. Daardoor hoeven we die niet handmatig op te slaan om er toegang toe te krijgen:

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

Samenstelling

Samenstelling is ook een “has-a”-relatie. Wat het onderscheidt van composition, is dat er geen sprake is van owning. Daardoor zijn de levenscycli van de objecten niet aan elkaar gebonden: ze kunnen allemaal onafhankelijk van elkaar bestaan.

Bijv. 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 zal prima werken.

Natuurlijk zal een auto zonder wielen of met een los wiel niet zo bruikbaar zijn als een auto met zijn wielen erop. Maar dat is waarom deze relatie in de eerste plaats bestond: om de onderdelen samen te voegen tot een grotere constructie, die tot meer dingen in staat is dan zijn onderdelen.

Omdat aggregatie geen eigendom inhoudt, hoeft een lid niet gebonden te zijn aan slechts één container. Bijvoorbeeld, een driehoek is gemaakt van segmenten. Maar driehoeken kunnen segmenten delen als hun zijden.

3.1. UML

Agggregatie lijkt erg op compositie. Het enige logische verschil is dat aggregatie een zwakkere relatie is.

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

Voor auto’s en wielen zouden we dan doen:

3.2. Broncode

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

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

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

In het bovenstaande codefragment hebben beide klassen hun aparte bronbestand. We kunnen echter ook een statische binnenklasse gebruiken:

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

Merk op dat Java alleen in niet-statische binnenklassen een impliciete verwijzing zal maken. Daarom moeten we de relatie handmatig onderhouden waar we hem nodig hebben:

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

Association

Association is de zwakste relatie tussen de drie. Het is geen “heeft-een” relatie, geen van de objecten zijn delen 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 in UML weergeven, dan:

4.2. Broncode

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

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

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

Wel, dat kunnen we niet. Het verschil is alleen logisch: of een van de objecten deel uitmaakt van het andere of niet.

Ook moeten we de referenties aan beide kanten handmatig onderhouden, net als bij aggregatie:

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

UML Sidenote

Voor de duidelijkheid willen we soms de cardinaliteit van een relatie op een UML-diagram vastleggen. We kunnen dit doen door het aan de uiteinden van de pijl te schrijven:

Merk op, dat het geen zin heeft om nul als cardinaliteit te schrijven, omdat dit betekent dat er geen relatie is. De enige uitzondering is wanneer we een bereik willen gebruiken om een optionele relatie aan te geven:

Ook moeten we opmerken dat, omdat er in een samenstelling precies één eigenaar is, we die niet op de diagrammen aangeven.

Een complex voorbeeld

Laten we eens een (iets) complexer voorbeeld bekijken!

We modelleren een universiteit, die haar afdelingen heeft. In elke afdeling werken professoren, die ook vrienden onder elkaar hebben.

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

Maar de professoren zullen nog steeds bestaan (hopelijk). We moeten beslissen wat logischer is: beschouwen we de hoogleraren als onderdelen van de afdelingen of niet. Of anders: zijn ze lid van de afdelingen of niet? Ja, dat zijn ze. Vandaar dat het een aggregatie is. Daar komt nog bij dat een hoogleraar in meerdere vakgroepen kan werken.

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

Hieruit volgt dat we dit voorbeeld kunnen modelleren met het volgende UML-diagram:

En de Java-code ziet er als volgt uit:

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

Merk op, dat als we ons baseren op de termen “has-a”, “belongs-to”, “member-of”, “part-of”, enzovoorts, we de relaties tussen onze objecten gemakkelijker kunnen vaststellen.

Conclusie

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

Zoals gebruikelijk zijn de voorbeelden beschikbaar op GitHub.

Ga aan de slag met Spring 5 en Spring Boot 2, via de cursus Learn Spring:

>> CHECK OUT THE COURSE

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *