I principi di progettazione SOLID introdotti per la prima volta da Robert Martin (più familiare tra sviluppatori come Zio Bob) nel suo documento del 2000 Design Principles and Design Patterns. I principi sono stati successivamente nominati da Michael Feathers che ha cambiato il loro ordine in modo da poter formare l'acronimo. Riguardano le considerazioni per le implementazioni delle interfacce, l'ereditarietà delle classi, la progettazione dei moduli e quanta responsabilità dovrebbe avere un oggetto. Questi principi si sono evoluti nel corso di decenni con il contributo di ingegneri del software di tutto il mondo. Le loro intuizioni e direttive aiuteranno sicuramente qualsiasi programmatore a produrre software di qualità.
I principi di progettazione SOLID si concentrano su aspetti distinti della progettazione orientata agli oggetti. Nella loro applicazione, è noto che molti si sovrappongono nell'applicazione in quanto la violazione di uno rischia di violare un altro. Tenendo presente ciascuno di essi, la consapevolezza dei seguenti cinque principi come collettivo è l'ideale.
Questi principi danno molti vantaggi ai programmatori con qualsiasi livello di esperienza. Questi principi possono richiedere anche una vita per padroneggiarli, ma anche i principianti possono trarre beneficio da uno studio di base. Di seguito è riportata una breve introduzione a ciascuno con alcuni esempi.
Il principio di responsabilità unica è il primo dei cinque principi di progettazione SOLID e afferma che una classe o un oggetto dovrebbe avere un'unica responsabilità all'interno della funzionalità complessiva di un programma. Se la tua classe è responsabile dell'acquisizione dei dati degli utenti dal database, non dovrebbe preoccuparsi di visualizzare anche i dati. Queste sono responsabilità diverse e dovrebbero essere gestite separatamente. Avere più responsabilità significa avere più ragioni per cambiare, il che significa un maggiore potenziale di rottura man mano che la propria base di codice cresce. Il Principio di Responsabilità Unica viene paragonato al concetto di una stanza pulita e organizzata. Avere i tuoi vestiti buttati in giro rappresenta il codice Spaghetti. È difficile tornare in una stanza disordinata un anno dopo e trovare ciò di cui hai bisogno, giusto? Hai bisogno di sistemare le cose. C'è un posto per ogni cosa nella tua stanza e ogni cosa dovrebbe essere al suo posto. Quindi anche nel tuo codice sorgente ogni cosa deve trovarsi al suo posto. Scrivi classi piccole con nomi molto specifici rispetto a classi grandi con nomi generici. Ad esempio, invece di buttare tutto all'interno di una classe Employee, separa le responsabilità in EmployeeTimeLog, EmployeeTimeOff, EmployeeSalary, EmployeeDetails, ecc. In questo modo hai un posto designato per tutto e sai esattamente dove guardare quando torni sul tuo codice anche dopo un lungo periodo.
Il principio aperto/chiuso (OCP) di SOLID afferma che le entità di programmazione come classi, funzioni o metodi dovrebbero essere progettate in modo da consentire l'aggiunta di funzionalità specifiche senza richiedere la modifica delle entità correlate. Applicare il principio aperto/chiuso è come lavorare con una libreria open source. Quando installi un pacchetto open source nel tuo progetto, non ne modifichi il codice. Invece, dipendi dal suo comportamento ma scrivi il tuo codice attorno ad esso. Ad un certo punto, la libreria viene testata così tanto che diventa molto stabile. Questo è ancora un po' concettuale, quindi proviamo a vederlo nel dettaglio con un esempio pratico. Supponiamo che tu debba implementare un modulo di e-commerce con più opzioni di pagamento. Puoi creare una classe Pagamenti e aggiungere diverse opzioni di pagamento come metodi separati. Funzionerebbe, ma comporterebbe la modifica della classe ogni volta che vogliamo aggiungere o modificare un'opzione di pagamento.
Prova invece a crearne uno PagamentiInterface che guidi l'implementazione di ciascuna opzione di pagamento. Quindi, per ogni opzione di pagamento, avrai una classe separata che implementa quell'interfaccia. Ora il nucleo del tuo sistema è chiuso per la modifica (non è necessario cambiarlo ogni volta che aggiungi una nuova opzione di pagamento), ma aperto per l'estensione (puoi aggiungere nuove opzioni di pagamento aggiungendo nuove classi).
Il principio di sostituzione di Liskov è stato introdotto per la prima volta da Barbara Liskov (un'informatica americana) alla fine degli anni '80. La formula matematica è questa:
"Sia φ(x) una proprietà dimostrabile sugli oggetti x di tipo T. Allora φ(y) dovrebbe essere vero per oggetti y di tipo S dove S è un sottotipo di T."
Oppure, in termini di ingegneria del software, dovresti essere in grado di sostituire una classe padre con una qualsiasi delle sue classi figlio, senza interrompere il sistema. In parole più semplici, le implementazioni della stessa interfaccia non dovrebbero mai dare un risultato diverso. Ad esempio, il padre è un insegnante mentre suo figlio è un medico. Quindi, in questo caso, il figlio non può semplicemente sostituire suo padre anche se entrambi appartengono alla stessa famiglia.
Il principio di segregazione dell'interfaccia afferma che non si dovrebbe mai forzare il client a dipendere da metodi che non utilizza. Immagina di avere una grande classe, Employee, di dipendenti con tutti i metodi necessari per gestire i dipendenti nel tuo sistema. Quindi, hai più controller progettati in modo RESTful, uno per ogni funzionalità di cui hai bisogno sul tuo sito web. Ora, tutti questi controller dipendono dalla classe Employee per la gestione delle rispettive funzionalità. Cambiare qualcosa può rompere tutti questi controller perché dipendono tutti da esso. Per ovviare tale problematica dividi la classe Employee in classi più piccole con interfacce specifiche. Dopo averlo fatto, regola i controller in modo che dipendano tutti solo dalle interfacce di cui hanno bisogno. In questo modo, avrai una struttura pulita in cui il client dipende solo dai metodi che utilizza. Cambiare qualcosa in una certa classe influenzerà solo le parti del sistema che effettivamente dipendono da essa.
Il principio di inversione delle dipendenze (DIP) si avvicina al buon design in un duplice modo. In primo luogo, stabilisce che i pacchetti/moduli di alto livello non dovrebbero dipendere da moduli di basso livello ma che entrambi dovrebbero dipendere da astrazioni. In secondo luogo, afferma che i dettagli dei costrutti software dovrebbero dipendere dalle astrazioni, non viceversa. Usiamo un esempio per spiegarlo. Guardando in casa, stai usando l'elettricità per collegare il tuo laptop, il tuo telefono, la lampada, ecc. Hanno tutti un'interfaccia unificata per ottenere l'elettricità: la presa. Il bello è che non devi preoccuparti del modo in cui viene fornita l'elettricità. Ti affidi semplicemente al fatto che puoi usarlo quando necessario. Ora, immagina se invece di dipendere dalla presa come interfaccia, dovessi collegare le cose ogni volta che hai bisogno di caricare il tuo telefono. In termini di software, è quello che facciamo ogni volta che dipendiamo da implementazioni concrete: sappiamo troppo su come le cose vengono implementate, il che rende facile violare il sistema. Combinando la tecnica di iniezione delle dipendenze con il concetto di associazione di un'interfaccia a un'implementazione concreta, puoi assicurarti di non dipendere mai da classi concrete. Ciò ti consentirà di modificare facilmente l'implementazione di parti specifiche del sistema senza romperlo. Un buon esempio di ciò è il passaggio del driver del database da SQL a NoSQL. Se dipendi dalle interfacce astratte per l'accesso al database, sarai in grado di modificare facilmente le implementazioni specifiche e assicurarti che il sistema funzioni correttamente.
I principi di progettazione SOLID intendono essere una guida per la progettazione di software SOLID di facile manutenzione, estensione e comprensione. Se usati correttamente, aiuteranno il tuo team a dedicare meno tempo a capire cosa fa il codice e più tempo a creare fantastiche funzionalità.