Composizione, aggregazione e associazione in Java

1. 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 sulla versione di Java di tre tipi di relazioni a volte facilmente confondibili: composizione, aggregazione e associazione.

2. Composizione

La 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.

In alternativa, spesso la chiamiamo una relazione "ha-un" (in opposizione a una relazione "è-un", che è eredità).

Ad esempio, una stanza appartiene a un edificio, o in altre parole un edificio ha una stanza. Quindi, fondamentalmente, se lo chiamiamo "appartiene a" o "ha-a" è solo una questione di punti di vista.

La composizione è un tipo forte di relazione "ha-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 verranno distrutti con esso. Ad esempio, la stanza viene distrutta con l'edificio nel nostro esempio precedente.

Notare che ciò non significa che l'oggetto contenitore non possa esistere senza nessuna delle sue parti. Ad 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 contenitore 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 si trova sull'oggetto contenitore ed è la base della linea, non una punta di freccia. Per motivi di chiarezza, spesso disegniamo anche la punta della freccia:

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

2.2. Codice sorgente

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

class Building { class Room {} }

In alternativa, possiamo dichiarare anche quella classe in un corpo del metodo. Non importa se si tratta di una classe denominata, 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 non sia statica poiché lega tutte le sue istanze alla classe contenente.

Di solito, l'oggetto contenitore desidera accedere ai suoi membri. Pertanto, dovremmo memorizzare i loro riferimenti:

class Building { List rooms; class Room {} }

Notare che tutti gli oggetti della classe interna memorizzano un riferimento implicito al loro oggetto contenitore. Di conseguenza, non è necessario memorizzarlo manualmente per accedervi:

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

3. Aggregazione

L'aggregazione è anche una relazione "ha-un". Ciò che lo distingue dalla composizione, che non implica il possesso. Di conseguenza, i cicli di vita degli oggetti non sono legati: ognuno di loro può esistere indipendentemente l'uno dall'altro.

Ad esempio, un'auto e le sue ruote. Possiamo togliere le ruote e esisteranno ancora. Possiamo montare altre ruote (preesistenti) o installarle su un'altra macchina e tutto funzionerà perfettamente.

Naturalmente, un'auto senza ruote o una ruota staccata non sarà utile come un'auto con le ruote montate. Ma è per questo che esisteva questa relazione in primo luogo: assemblare le parti in un costrutto più grande, che è capace di più cose delle sue parti .

Poiché l'aggregazione non implica il possesso, un membro non deve essere collegato a un solo contenitore . Ad esempio, un triangolo è costituito da segmenti. Ma i triangoli possono condividere segmenti come i loro lati.

3.1. UML

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

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

Per le auto e le ruote, quindi, faremmo:

3.2. Codice sorgente

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

class Wheel {} class Car { List 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 wheels; static class Wheel {} }

Si noti 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 wheels; }

4. Associazione

L'associazione è la relazione più debole tra i tre. Non è una relazione "ha-un" , nessuno degli oggetti è parte o membro di un altro.

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

4.1. UML

In UML, possiamo contrassegnare un'associazione con una freccia:

Se l'associazione è bidirezionale, possiamo utilizzare due frecce, una freccia con una punta di freccia su entrambe le estremità o una linea senza punte di freccia:

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.

Il rapporto tra professori è associazione perché non ha alcun senso dire che un professore fa parte di un altro.

Di conseguenza, possiamo modellare questo esempio con il seguente diagramma UML:

E il codice Java ha questo aspetto:

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

Nota che se ci affidiamo ai termini "ha-a", "appartiene-a", "membro-di", "parte di" e così via, possiamo identificare più facilmente le relazioni tra i nostri oggetti.

7. 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.