La maggior parte degli sviluppatori di software professionisti conoscono le definizioni accademiche di accoppiamento, coesione e incapsulamento. Nonostante ciò, molti di questi si trovano in difficoltà quando devono mettere in pratica questi principi e trarre quindi i vantaggi da concetti come basso accoppiamento , alta coesione e forte incapsulamento. In questo articolo ci proponiamo di rendere più chiari questi argomenti, e di come si possa scrivere codice migliore sfruttando quelli che sono i 5 principi di progettazione OOD conosciuti sotto l’acronimo S. O. L. I. D. che danno le linee guida per la progettazione di “buone” classi.
Indice
Facciamo “mente locale”
Basso accoppiamento (low coupling)
L’accoppiamento nel contesto dello sviluppo di software è definita come il grado in cui un modulo, una classe, o altro costrutto, è legato direttamente ad altri. Per esempio, il grado di accoppiamento tra due classi può essere visto come la misura di quanto una classe dipende da un’altra. Se una classe è strettamente accoppiata (tight o high coupling) con una o più classi, vuol dire che è necessario utilizzare tutte le classi che sono accoppiate quando si desidera utilizzare anche solo una di loro. È possibile ridurre l’accoppiamento mediante la definizione di standard per le connessioni e di interfacce tra le varie parti di un sistema. Questo, in un linguaggio OO, avviene concretamente definendo delle classi astratte e delle interfacce che ben definiscono i punti di comunicazione tra le varie parti.
Alta Coesione (High Cohesion)
La coesione è la misura in cui due o più parti di un sistema sono correlate e come queste lavorano insieme per creare qualcosa di maggior valore rispetto a quello che fa ogni singola parte. In un sistema software siffatto, è ovvio che non è possibile creare un’alta coesione se si usano classi che fanno troppe cose e che hanno quindi molte responsabilità. Questo tipo di classi tendono ad essere autoreferenziali e la coesione con l’esterno diventa difficile da realizzare.
Incapsulamento (encapsulation)
I principi S.O.L.I.D.
I 5 principi SOLID si focalizzano principalmente sulla gestione delle dipendenze più che sugli aspetti strettamente legati alla modelllazione in se. Una buona gestione delle dipendenze porta a codice maggiormente flessibile, robusto e riutilizzabile.
Vediamo adesso l’elenco e una breve descrizione di questi 5 principi che verranno poi analizzati in dettaglio uno per uno.
SRP | The Single Responsibility Principle | Una classe dovrebbe avere uno ed unico motivo per cambiare |
OCP | The Open Closed Principle | Una qualsiasi entità software (classe, modulo, funzione, ecc.) dovrebbe avere meccanismi che permettono di estenderne il comportamento senza apportare modifiche al codice preesistente. Quindi Aperte alle estensioni ma chiuse alle modifiche; da qui il nome Open-Closed. |
LSP | The Liskov Substitution Principle | Le classi derivate devono sempre poter essere sostituite dalle classi da cui queste derivano (superclassi) in maniera trasparente. |
ISP | The Interface Segregation Principle | Una classe client non dovrebbe dipendere da metodi che non usa, e che pertanto è preferibile che le interfacce siano molte, specifiche e piccole (composte da pochi metodi) piuttosto che poche, generali e grandi. |
DIP | The Dependency Inversion Principle | Una classe dovrebbe dipendere da astrazioni e non da concrete e specifiche implementazioni. |
SRP – Single Responsibility Principle o principio di singola responsabilità
Il Principio di singola responsabilità dice che ogni oggetto si deve focalizzare su una singola responsabilità e deve avere quindi un solo motivo per cambiare. In altre parole, ogni oggetto deve eseguire una cosa sola. È possibile applicare questa idea a diversi livelli del software: un metodo deve solo compiere una azione; un oggetto di dominio dovrebbe rappresentare solo una entità all’interno di tale dominio; il livello di presentazione (GUI) dovrebbe essere responsabile della presentazione dei dati; ecc. Questo principio mira a raggiungere i seguenti obiettivi:
- Oggetti piccoli e concisi: serve ad evitare il problema di classi enormi e monolitiche che sono l’equivalente software di un coltellino Svizzero.
- Testabilità: se un metodo svolge più di una attività, è più difficile scrivere un test
- Leggibilità: la lettura di codice breve e conciso è certamente più facile che trovare un senso attraverso un groviglio di “spaghetti code”
- Manutenzione più semplice
Una responsabilità di una classe, di solito, rappresenta una caratteristica o un entità del dominio, nella vostra applicazione. Se si assegnano molte responsabilità ad una classe c’è una maggiore probabilità che avrete bisogno di cambiarla. Queste responsabilità sono accoppiate insieme nella classe, rendendo ogni responsabilità individuale più difficile da cambiare senza introdurre errori nel resto. In questo contesto è ragionevole associare una responsabilità a un “motivo per cambiare”.
SRP è fortemente legato a quello che viene chiamato Separation of Concerns (SoC) cioè la separazione delle responsabilità. SoC significa sezionare un pezzo di software in distinte caratteristiche che incapsulano l’unico comportamento e i dati che possono essere utilizzati dalle altre classi. La separazione di un programma in piccole parti, ciascuna con la propria responsabilità, aumenta significativamente il riutilizzo del codice, la manutenzione e la testabilità.
Vedendolo da punto di vista della coesione e accoppiamento vediamo che qui si ha un elevato livello di coesione e un basso livello di accoppiamento dovuti alla semplicità e alla singola responsabilità delle classi.
OCP – Open Closed Principle o Principio Aperto-Chiuso
Ora è il momento di passare alla lettera ” O ” che sta per Open-Closed principle (OCP). OCP afferma che le classi dovrebbero essere aperte per l’estensione e chiuse per la modifica. Si dovrebbe essere in grado di aggiungere nuove funzionalità e di estendere una classe senza cambiare il suo comportamento interno . È sempre possibile aggiungere nuovi comportamenti ad una classe ma allo stesso tempo, non deve essere necessario ricompilare tutta l’applicazione solo per fare spazio a cose nuove. L’obiettivo principale di questo principio è quello di evitare di introdurre bug ed errori in genere alle funzionalità esistenti a seguito dell’aggiunta di altre funzionalità nella classe. Come è possibile fare questo? La chiave del successo è quello di identificare le aree del dominio maggiormente soggette a cambiamenti e operare delle astrazioni con l’utilizzo di classi astratte e interfacce. Con l’utilizzo di astrazioni si separa l’interfaccia di un oggetto dalla sua implementazione. Eventuali cambiamenti di implementazione non si riflettono sulle classi che usano una classe, in quanto l’interfaccia rimane invariata. Eventuali aggiunte si traducono in un estensione dell’interfaccia e relativa implementazione che è trasparente per le classi che usano i “vecchi” metodi dell’interfaccia.
LSP – Liskov Substitution Principle o principio di sostituzione di Liskov
Dopo aver visto le lettere ‘S‘ e ‘O‘ di SOLID, è il momento di discutere di ciò che la ‘L’ ha da offrire. L sta per Principio di Sostituzione di Liskov (LSP) e afferma che si dovrebbe essere sempre in grado di utilizzare qualsiasi classe derivata al posto di una classe genitore (superclasse) senza apportare alcuna modifica. Il principio garantisce che una classe derivata non influenzi il comportamento della classe padre, vale a dire che se abbiamo, per esempio, una funzione che prende in input la classe base A per operare su di essa, la stessa funzione deve essere in grado di lavorare con la classe B, sottoclasse di A, senza necessariamente conoscere i dettagli della classe B.
Il principio prende il nome da Barbara Liskov, che per prima ha descritto il problema nel 1988.
Altro caso per capire meglio il principio è il seguente: se una classe di base definisce due metodi astratti una sua classe derivata deve dare significative implementazioni di entrambi i metodi astratti. Se una classe derivata implementa un metodo astratto lanciando un eccezione del tipo NotImplementedException, perchè probabilmente nel contesto della classe derivata tale metodo non ha senso, allora vuol dire che la classe derivata non potrà completamente sostituire la sua classe di base e inoltre questo potrebbere essere segno che la classe base non un “vera” classe base o astrazione e che probabilmente va rivista la propria gerarchia delle classi.
Tutti coloro che studiano OOP ad un certo punto hano a che fare con la relazione “E’ un” (IS-A), e quindi con il concetto di classe base e classi derivate: un Cane è un Animale, un Impiegato è un Dipendente che è una Persona, un Auto è un veicolo ecc. LSP affina questa relazione con una cosa del tipo “può sostituire un”, il che significa che un oggetto è sostituibile con un altro oggetto in tutte le situazioni, senza nessuna eccezione.
ISP – Interface Segregation Principle o principio di segregazione delle interfacce
Questo principio afferma che una classe client non dovrebbe dipendere da metodi che non usa, e che pertanto è preferibile che le interfacce siano molte, specifiche e piccole (composte da pochi metodi) piuttosto che poche, generali e grandi. Questo consente a ciascun client di dipendere da un insieme minimo di metodi, ovvero quelli appartenenti alle interfacce che effettivamente usa. In ossequio a questo principio, un oggetto dovrebbe tipicamente implementare numerose interfacce, una per ciascun ruolo che l’oggetto stesso gioca nei diversi contesti o diverse interazioni con altri oggetti.
DIP- Dependency Inversion Principle o principio di inversione delle dipendenze
Un altro termine correlato alla DIP è Inversion of Control (IoC), che indica più o meno lo stesso tpo di concetto. Inversion of Control o Dependency Injection è il principale pattern su cui si basa il famoso framework java Spring. Come suggerisce il nome, l’IoC sposta la responsabilità di gestire il ciclo di vita degli oggetti, come ad es. la creazione di oggetti, il setting di dipendenze, ecc dalla applicazione al framework. Vantaggi dell’uso della DI sono una riduzione dell’accoppiamento tra calssi, una maggiore flessibilità nei test, consentendo l’uso di mock object e maggiore flessibilità in quanto è possibile cambiare ad esempio una implementazione poco performante con una più performante in maniera semplice e trasparente per la classi client che ne fanno uso.
Conclusioni
Riferimenti
http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
Mai visto un blah blah tanto nozionistico. Maturate un poco di praticità anglossassone, per piacere. Tanti concetti astratti corredati da foto per cerebrolesi, errori ortografici e neanche l’ombra di un esempio. Alcune frasi, inoltre, fanno capire come chi ha scritto questo inutilissimo pezzo non abbia capito nulla dei principi SOLID. Meglio, ancora una volta, i siti stranieri. Che noia…
Caro Luca mi dispiace che tu non abbia apprezzato l’articolo, magari ti aspettavi qualcosa di diverso e mi scuso degli errori di battitura che ho corretto. Comunque l’articolo è volutamente di taglio teorico e vuole essere un tentativo di schematizzare in maniera concisa argomenti che forse è difficile sintetizzare in poche parole, pertanto non presenta esempi pratici che avrebbero allungato non di poco il pezzo, e poi, da come parli, mi sembri uno che non ha certo bisogno di esempi, visto che sembri così esperto dei principi SOLID. Saluti.
Questo blah blah blah nozionistico è fondamentale per capire la teoria, capra
In verità invece questo articolo è ottimo come sintesi. Naturalmente devi avere un po’ di esperienza di programmazione per capire queste cose senza esempi.
Capisco sia molto semplice e alla portata di tutti con i soliti esempi presenti ovunque, ma per gli esperti come te Luca non dovrebbe essere un problema.
Ciao complimenti per l’articolo e per il sito. Grazie per lo sforzo di racchiudere in un unico punto, in italiano, nozioni e concetti importanti nell’ambito dello sviluppo. Probabilmente non saranno cosi anglosassoni, ma fanno comunque bene visto che gli argomenti presentati non sono noti cosi spesso a molti sviluppatori.
Ciao Marco, grazie per l’apprezzamento.
Grazie mille per l articolo che in sintesi racchiude una teoria che difficilmente viene schematizzata in poche righe, per i commenti negativi, dovete solo ringraziare, ci sono numerosi professoroni universitari che per riassumere concetti così importanti gli ci vorrebbe una vita.
Mitici!
Grazie a te 😉
Grazie e complimenti.
E’ ottimo anche come ripasso per la teoria, che dopo anni di pratica, a volte si tende a dimenticare dandola per scontata.
Rileggere e ristudiare le definizioni, aiuta a rivedere il proprio lavoro e migliorarlo
Ogni mattina, qualsiasi persona abbia un ide installata, deve prendere questo articolo e rileggerlo almeno per tre anni.
Non sarà iperdettagliato, ma almeno la base per evitare certe schifezze che si vedono in giro la da a tutti
Articolo, a mio avviso, ben fatto.
Come già detto in un altro commento, dopo tanti anni con le mani immerse nel codice, alcune nozioni teoriche si danno per scontate.
Un bel ripassino è sempre gradito.
Grazie