Le relazioni tra oggetti

_Logo Continuo la discussione sulla programmazione orientata agli oggetti approfondendo il concetto di relazione. Applicando i tanti principi OOP disponibili, abbiamo visto come definire un progetto individuando le classi a partire dai requisiti. Gestire le relazioni significa ora organizzare le classi minimizzando le dipendenze.

Abbiamo visto nella precedente puntata come il principio di singola responsabilità porti a creare più classi a partire dalle prime individuate analizzando i requisiti iniziali. Abbiamo visto che questo è un processo iterativo e che ha come obiettivo il rendere il codice più pulito, mantenibile e testabile. Una delle regole era quella di avere il minor numero di relazioni.

E’ chiaro comunque che le classi definite devono prima o poi interagire tra loro e gestire le relazioni significa proprio pensare a questa interazione che ha come scopo l’esecuzione delle operazioni richieste all’applicazione. Così, se avrò le classi Customer, Order e una serie di classi di supporto come OrderItem, Address, PaymentDetail o di accesso ai dati come CustomerRepository e OrderRepository, esse dovranno essere utilizzate contemporaneamente in una procedura di creazione ordine.

Tenendo a mente tale struttura, che non dettaglio perché presuppongo che una struttura di questo tipo sia abbastanza banale, possiamo per prima cosa definire i tre tipi di relazioni esistenti:

  • Collaborazione (detta anche utilizza un): una classe utilizza un’altra classe per implementare il suo scopo. Quindi una classe CustomerRepository deve necessariamente utilizzare una classe Customer, perché il suo scopo è quello di salvare e recuperare gli oggetti Customer dalla base dati. Non è vero il contrario: la classe Customer non deve utilizzare la classe CustomerRepository perché il suo compito è quello di rappresentare (nel nostro sistema) un cliente e non quello di gestirne la storicizzazione.
  • Composizione (ha un): un oggetto è composto da più oggetti. Se tali oggetti esistono solo come parte dell’oggetto principale allora si parla di aggregazione: un OrderItem esiste solo come parte di un Order.
  • Derivazione (è un): un oggetto deriva parte delle sue caratteristiche da un’altra classe, la classe base. La classe derivata specializza la classe base, quindi una classe Customer potrebbe essere specializzata tramite classi del tipo BusinessCustomer o ResidentialCustomer.

Per la composizione possiamo avere:

  • Delle relazioni dirette, utilizzando i riferimenti ad oggetti secondari. In pratica una classe include il riferimento ad un’altra classe come proprietà. Nel Customer potrei avere il dettaglio dell’indirizzo tramite una classe Address.
  • Delle relazioni indirette, utilizzando una chiave che identifica l’oggetto collegato. Nella classe Order è possibile includere un CustomerId come semplice intero. Questa tecnica è utile per evitare di caricare tutti i dettagli di un oggetto, minimizza il cosiddetto coupling (quindi le dipendenze tra classi), velocizza l'accesso, semplifica la serializzazione e garantisce che l'oggetto padre non debba preoccuparsi degli oggetti referenziati. Segue inoltre il principio di singola responsabilità: per rappresentare l’Order è sufficiente l’Id del Customer: per altri scopi, come la visualizzazione, si dovrà utilizzare una classe apposita.

Quest’ultimo appunto è importante perché garantisce di avere classi chiare, mantenibili e testabili. Una classe OrderDisplay avrà quindi il compito di esporre all’utilizzatore tutte le proprietà da visualizzare. Inoltre, tali proprietà possono essere adeguatamente preparare in base a autorizzazioni, localizzazioni e altri motivi che non riguardano la classe Order in se. Nulla vieta ovviamente di avere più classi di visualizzazione per la stessa entità. Inoltre, sempre per limitare le dipendenze tra oggetti, è bene non includere in queste classi altre classi: se devo visualizzare con l’ordine solo il nome del cliente, la sua mail e la città, allora è meglio esporre proprietà specializzate come CustomerName, CustomerMail e CustomerCity, piuttosto che integrare l’intero Customer.

In questi casi è utile analizzare gli ambiti in cui una classe deve essere utilizzata, che sono generalmente: l’inserimento, la visualizzazione, l’aggiornamento e l’elaborazione. Per l’elaborazione potremmo avere che l’emissione di un ordine comporta la sua creazione, il salvataggio e la notifica.

Nella prima puntata abbiamo accennato che all’erediterietà come terzo pilastro dell'OOP ed è una tecnica fondamentale per il riutilizzo del codice Riguarda l'estrazione di codice comune che può essere utilizzato in più contesti e quindi con la creazione di classi e componenti riutilizzabili. Ed è anche, naturalmente, una forma di relazione tra oggetti e come tale deve essere correttamente utilizzata.

Tag: OOP