Composizione, aggregazione e associazione in Java

Introduzione

Gli oggetti hanno relazioni tra loro, sia nella vita reale che nella programmazione. A volte è difficile capire o implementare queste relazioni.

In questo tutorial, ci concentreremo sull’approccio di Java a tre tipi di relazioni a volte facilmente confuse: composizione, aggregazione e associazione.

Composizione

Composizione è un tipo di relazione “appartiene a”. Significa che uno degli oggetti è una struttura logicamente più grande, che contiene l’altro oggetto. In altre parole, è parte o membro dell’altro oggetto.

Alternativamente, spesso la chiamiamo una relazione “has-a” (in contrapposizione a una relazione “is-a”, che è l’ereditarietà).

Per esempio, una stanza appartiene a un edificio, o in altre parole un edificio ha una stanza. Quindi, fondamentalmente, se la chiamiamo “belongs-to” o “has-a” è solo una questione di punti di vista.

La composizione è un forte tipo di relazione “has-a” perché l’oggetto che la contiene la possiede. Pertanto, i cicli di vita degli oggetti sono legati. Significa che se distruggiamo l’oggetto proprietario, anche i suoi membri saranno distrutti con esso. Per esempio, la stanza viene distrutta con l’edificio nel nostro esempio precedente.

Nota che questo non significa che l’oggetto contenente non può esistere senza nessuna delle sue parti. Per esempio, possiamo abbattere tutti i muri all’interno di un edificio, quindi distruggere le stanze. Ma l’edificio esisterà ancora.

In termini di cardinalità, un oggetto contenente può avere tutte le parti che vogliamo. Tuttavia, tutte le parti devono avere esattamente un contenitore.

2.1. UML

In UML, indichiamo la composizione con il seguente simbolo:

Nota, che il diamante è all’oggetto contenente ed è la base della linea, non una punta di freccia. Per chiarezza, spesso disegniamo anche la punta della freccia:

Quindi, possiamo usare questo costrutto UML per il nostro esempio di Building-Room:

2.2. Codice sorgente

In Java, possiamo modellarlo con una classe interna non statica:

class Building { class Room {} }

In alternativa, possiamo dichiarare questa classe anche nel corpo di un metodo. Non importa se è una classe nominata, una classe anonima o una 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(); }}

Nota che è essenziale che la nostra classe interna sia non statica, poiché lega tutte le sue istanze alla classe contenente.

Di solito, l’oggetto contenente vuole accedere ai suoi membri. Pertanto, dovremmo memorizzare i loro riferimenti:

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

Nota che tutti gli oggetti della classe interna memorizzano un riferimento implicito al loro oggetto contenente. Di conseguenza, non abbiamo bisogno di memorizzarlo manualmente per accedervi:

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

Aggregazione

Anche l’aggregazione è una relazione “has-a”. Ciò che la distingue dalla composizione è che non implica il possesso. Di conseguenza, i cicli di vita degli oggetti non sono legati: ognuno di essi può esistere indipendentemente l’uno dall’altro.

Per esempio, una macchina e le sue ruote. Possiamo togliere le ruote, e continueranno ad esistere. Possiamo montare altre ruote (preesistenti), o installarle su un’altra auto e tutto funzionerà bene.

Ovviamente, un’auto senza ruote o con una ruota staccata non sarà utile come un’auto con le ruote. Ma questo è il motivo per cui questa relazione è esistita in primo luogo: per assemblare le parti ad un costrutto più grande, che è capace di più cose delle sue parti.

Siccome l’aggregazione non comporta il possedere, un membro non ha bisogno di essere legato ad un solo contenitore. Per esempio, un triangolo è fatto di segmenti. Ma i triangoli possono condividere segmenti come lati.

3.1. UML

L’aggregazione è molto simile alla composizione. L’unica differenza logica è che l’aggregazione è una relazione più debole.

Quindi, anche le rappresentazioni UML sono molto simili. L’unica differenza è che il diamante è vuoto:

Per auto e ruote, quindi, faremmo:

3.2. Codice sorgente

In Java, possiamo modellare l’aggregazione con un semplice riferimento:

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

Il membro può essere qualsiasi tipo di classe, tranne una classe interna non statica.

Nello snippet di codice sopra entrambe le classi hanno il loro file sorgente separato. Tuttavia, possiamo anche usare una classe interna statica:

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

Nota che Java creerà un riferimento implicito solo nelle classi interne non statiche. Per questo motivo, dobbiamo mantenere la relazione manualmente dove ne abbiamo bisogno:

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

Associazione

L’associazione è la relazione più debole tra le tre. Non è una relazione “ha-un”, nessuno degli oggetti è parte o membro di un altro.

Associazione significa solo che gli oggetti si “conoscono”. Per esempio, una madre e suo figlio.

4.1. UML

In UML, possiamo marcare un’associazione con una freccia:

Se l’associazione è bidirezionale, possiamo usare due frecce, una freccia con una punta di freccia alle due estremità, o una linea senza alcuna punta di freccia:

Possiamo rappresentare una madre e suo figlio in UML, quindi:

4.2. Codice sorgente

In Java, possiamo modellare l’associazione allo stesso modo dell’aggregazione:

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

Ma aspetta, come possiamo dire se un riferimento significa aggregazione o associazione?

Beh, non possiamo. La differenza è solo logica: se uno degli oggetti è parte dell’altro o no.

Inoltre, dobbiamo mantenere i riferimenti manualmente su entrambe le estremità come abbiamo fatto con l’aggregazione:

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

UML Sidenote

Per chiarezza, a volte vogliamo definire la cardinalità di una relazione su un diagramma UML. Possiamo farlo scrivendola alle estremità della freccia:

Nota, che non ha senso scrivere zero come cardinalità, perché significa che non c’è nessuna relazione. L’unica eccezione è quando vogliamo usare un intervallo per indicare una relazione opzionale:

Nota anche che, poiché nella composizione c’è precisamente un proprietario, non lo indichiamo nei diagrammi.

Un esempio complesso

Vediamo un esempio (poco) più complesso! In ogni dipartimento lavorano dei professori, che hanno anche degli amici tra di loro.

I dipartimenti esisteranno dopo che avremo chiuso l’università? Ovviamente no, quindi è una composizione.

Ma i professori esisteranno ancora (si spera). Dobbiamo decidere cosa è più logico: se consideriamo i professori come parte dei dipartimenti o no. In alternativa: sono membri dei dipartimenti o no? Sì, lo sono. Quindi è un’aggregazione. Inoltre, un professore può lavorare in più dipartimenti.

La relazione tra professori è un’associazione perché non ha senso dire che un professore fa parte di un altro.

Come risultato, possiamo modellare questo esempio con il seguente diagramma UML:

E il codice Java appare così:

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

Nota, se ci basiamo sui termini “has-a”, “belongs-to”, “member-of”, “part-of”, e così via, possiamo identificare più facilmente le relazioni tra i nostri oggetti.

Conclusione

In questo articolo, abbiamo visto le proprietà e la rappresentazione di composizione, aggregazione e associazione. Abbiamo anche visto come modellare queste relazioni in UML e Java.

Come al solito, gli esempi sono disponibili su GitHub.

Inizia con Spring 5 e Spring Boot 2, attraverso il corso Learn Spring:

>> CHECK OUT THE COURSE

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *