La Single Responsibility (SRP), Open / Closed (OCP), Liskov Substitution, Interface Segregation e Inversione di dipendenza. Cinque principi agili che dovrebbero guidarti ogni volta che scrivi il codice.
Sarebbe ingiusto dirti che uno qualsiasi dei principi SOLIDI è più importante di un altro. Tuttavia, probabilmente nessuno degli altri ha un effetto così immediato e profondo sul tuo codice rispetto al principio di inversione della dipendenza, o in breve DIP. Se ritieni che gli altri principi siano difficili da comprendere o applicare, inizia con questo e applica il resto sul codice che rispetta già DIP.
A. I moduli di alto livello non dovrebbero dipendere da moduli di basso livello. Entrambi dovrebbero dipendere dalle astrazioni.
B. Le astrazioni non dovrebbero dipendere dai dettagli. I dettagli dovrebbero dipendere dalle astrazioni.
Questo principio è stato definito da Robert C. Martin nel suo libro Agile Software Development, Principles, Patterns and Practices e successivamente ripubblicato nella versione C # del libro Agile Principles, Patterns and Practices in C #, ed è l'ultimo dei cinque SOLIDI principi agili.
Prima di iniziare a programmare, vorrei raccontarti una storia. In Syneto, non siamo sempre stati così attenti con il nostro codice. Qualche anno fa ne sapevamo di meno e anche se cercavamo di fare del nostro meglio, non tutti i nostri progetti erano così belli. Abbiamo attraversato l'inferno e ritorno e abbiamo imparato molte cose per tentativi ed errori.
I principi SOLID e i principi di architettura pulita dello zio Bob (Robert C. Martin) sono diventati un punto di svolta per noi e hanno trasformato il nostro modo di codificare in modi difficili da descrivere. Proverò ad esemplificare, in poche parole, alcune decisioni architettoniche chiave imposte dal DIP che hanno avuto un grande impatto sui nostri progetti.
La maggior parte dei progetti web contiene tre tecnologie principali: HTML, PHP e SQL. La particolare versione di queste applicazioni di cui stiamo parlando o il tipo di implementazione SQL che usi è irrilevante. Il fatto è che le informazioni da un modulo HTML devono finire, in un modo o nell'altro, nel database. La colla tra i due può essere fornita con PHP.
Ciò che è essenziale rimuovere da questo, è che quanto bene le tre tecnologie rappresentano tre diversi livelli architettonici: interfaccia utente, logica aziendale e persistenza. Parleremo delle implicazioni di questi strati in un minuto. Per ora, concentriamoci su alcune soluzioni strane ma frequentemente riscontrate per far funzionare le tecnologie insieme.
Molte volte ho visto progetti che utilizzavano codice SQL in un tag PHP all'interno di un file HTML, o codice PHP che riecheggiava pagine e pagine di HTML e interpretava direttamente il codice $ _GET
o $ _POST
variabili globali. Ma perché è così male?
Le immagini sopra rappresentano una versione grezza di ciò che abbiamo descritto nel paragrafo precedente. Le frecce rappresentano varie dipendenze e, come possiamo concludere, praticamente tutto dipende da tutto. Se abbiamo bisogno di cambiare una tabella di database, potremmo finire per modificare un file HTML. O se cambiamo un campo in HTML, potremmo finire per cambiare il nome di una colonna in un'istruzione SQL. Oppure, se osserviamo il secondo schema, potremmo avere bisogno di modificare il nostro PHP se l'HTML cambia o, in casi molto brutti, quando generiamo tutto il contenuto HTML da un file PHP, avremo sicuramente bisogno di cambiare un file PHP in modificare il contenuto HTML. Quindi, non c'è dubbio, le dipendenze sono zigzag tra le classi e i moduli. Ma non finisce qui. È possibile memorizzare le procedure; Codice PHP nelle tabelle SQL.
Nello schema precedente, le query al database SQL restituiscono il codice PHP generato con i dati delle tabelle. Queste funzioni o classi PHP stanno facendo altre query SQL che restituiscono un codice PHP diverso, e il ciclo continua fino a quando tutte le informazioni sono state ottenute e restituite ... probabilmente all'interfaccia utente.
So che questo può sembrare scandaloso per molti di voi, ma se non avete ancora lavorato con un progetto inventato e implementato in questo modo, sicuramente lo farete nella vostra futura carriera. La maggior parte dei progetti esistenti, a prescindere dai linguaggi di programmazione utilizzati, sono stati scritti tenendo conto dei vecchi principi, dai programmatori a cui non importava o che non conoscevano abbastanza da fare meglio. Se stai leggendo questi tutorial, probabilmente sei un livello più alto di quello. Sei pronto o ti stai preparando a rispettare la tua professione, ad abbracciare il tuo mestiere e a fare meglio.
L'altra opzione è ripetere gli errori fatti dai tuoi predecessori e vivere con le conseguenze. A Syneto, dopo che uno dei nostri progetti ha raggiunto uno stato quasi inviolabile a causa della sua architettura antica e interdipendente e abbiamo dovuto abbandonarlo per sempre, abbiamo deciso di non tornare mai più su quella strada. Da allora, ci siamo sforzati di avere un'architettura pulita che rispetti correttamente i principi SOLID e, soprattutto, il principio di inversione delle dipendenze.
La cosa sorprendente di questa architettura è il modo in cui le dipendenze stanno puntando:
Applicare il principio di inversione di dipendenza (DIP) a livello di architettura è abbastanza semplice se si rispettano i classici schemi di progettazione agili. Esercitare ed esemplificare all'interno della logica di business è abbastanza facile e può anche essere divertente. Immagineremo un'applicazione per e-book reader.
class Test estende PHPUnit_Framework_TestCase function testItCanReadAPDFBook () $ b = new PDFBook (); $ r = new PDFReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ()); class PDFReader private $ book; function __construct (PDFBook $ book) $ this-> book = $ book; function read () return $ this-> book-> read (); class PDFBook function read () return "legge un libro in formato PDF.";
Iniziamo a sviluppare il nostro e-reader come lettore PDF. Fin qui tutto bene. Noi abbiamo un PdfReader
classe usando a PDFBook
. Il leggere()
funzione sul lettore delegati al libro leggere()
metodo. Lo verifichiamo semplicemente eseguendo un controllo regex dopo che una parte chiave della stringa restituita da PDFBook
'S lettore()
metodo.
Si prega di tenere presente che questo è solo un esempio. Non implementeremo la logica di lettura di file PDF o altri formati di file. Ecco perché i nostri test controllano semplicemente alcune stringhe di base. Se dovessimo scrivere la vera applicazione, l'unica differenza sarebbe il modo in cui testiamo i diversi formati di file. La struttura delle dipendenze sarebbe molto simile al nostro esempio.
Avere un lettore PDF utilizzando un libro PDF può essere una soluzione valida per un'applicazione limitata. Se il nostro scopo fosse quello di scrivere un lettore PDF e nient'altro, sarebbe in realtà una soluzione accettabile. Ma vogliamo scrivere un lettore di e-book generico, supportando diversi formati, tra cui la nostra prima versione PDF implementata. Rinominiamo la nostra classe di lettori.
class Test estende PHPUnit_Framework_TestCase function testItCanReadAPDFBook () $ b = new PDFBook (); $ r = nuovo EBookReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ()); class EBookReader private $ book; function __construct (PDFBook $ book) $ this-> book = $ book; function read () return $ this-> book-> read (); class PDFBook function read () return "legge un libro in formato PDF.";
La ridenominazione non ha avuto effetti contrattuali funzionali. I test stanno ancora passando.
I test sono iniziati alle 13:04 ...
PHPUnit 3.7.28 di Sebastian Bergmann.
Tempo: 13 ms, Memoria: 2,50 Mb
OK (1 test, 1 asserzione)
Processo terminato con codice di uscita 0
Ma ha un effetto di design serio.
Il nostro lettore è diventato molto più astratto. Molto più generale. Abbiamo un generico Lettore di ebook
che usa un tipo di libro molto specifico, PDFBook
. Un'astrazione dipende da un dettaglio. Il fatto che il nostro libro sia di tipo PDF dovrebbe essere solo un dettaglio e nessuno dovrebbe dipendere da esso.
class Test estende PHPUnit_Framework_TestCase function testItCanReadAPDFBook () $ b = new PDFBook (); $ r = nuovo EBookReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ()); interfaccia EBook function read (); class EBookReader private $ book; function __construct (EBook $ book) $ this-> book = $ book; function read () return $ this-> book-> read (); classe PDFBook implementa EBook function read () return "leggendo un libro in formato pdf.";
La soluzione più comune e più frequentemente utilizzata per invertire la dipendenza è introdurre un modulo più astratto nel nostro design. "L'elemento più astratto di OOP è un'interfaccia, quindi qualsiasi altra classe può dipendere da un'interfaccia e rispettare il DIP".
Abbiamo creato un'interfaccia per il nostro lettore. L'interfaccia è chiamata EBook
e rappresenta i bisogni del Lettore di ebook
. Questo è un risultato diretto del rispetto del principio di segregazione dell'interfaccia (ISP) che promuove l'idea che le interfacce debbano riflettere le esigenze dei clienti. Le interfacce appartengono ai client e pertanto vengono denominate per riflettere i tipi e gli oggetti di cui i client hanno bisogno e conterranno i metodi che i client desiderano utilizzare. È naturale per un Lettore di ebook
usare EBooks
e avere un leggere()
metodo.
Invece di una singola dipendenza, ora abbiamo due dipendenze.
Lettore di ebook
verso il EBook
interfaccia ed è di tipo uso. Lettore di ebook
usi EBooks
.PDFBook
verso lo stesso EBook
interfaccia ma è di tipo implementazione. UN PDFBook
è solo una forma particolare di EBook
, e quindi implementa quell'interfaccia per soddisfare le esigenze del cliente.Non sorprende che questa soluzione ci consenta anche di inserire diversi tipi di ebook nel nostro lettore. La sola condizione per tutti questi libri è soddisfare il EBook
interfaccia e implementalo.
class Test estende PHPUnit_Framework_TestCase function testItCanReadAPDFBook () $ b = new PDFBook (); $ r = nuovo EBookReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ()); function testItCanReadAMobiBook () $ b = new MobiBook (); $ r = nuovo EBookReader ($ b); $ this-> assertRegExp ('/ mobi book /', $ r-> read ()); interfaccia EBook function read (); class EBookReader private $ book; function __construct (EBook $ book) $ this-> book = $ book; function read () return $ this-> book-> read (); classe PDFBook implementa EBook function read () return "leggendo un libro in formato pdf."; classe MobiBook implementa EBook function read () return "leggendo un libro mobi.";
Che a sua volta ci porta a The Open / Closed Principle e il cerchio è chiuso.
Il principio di inversione di dipendenza è uno che guida o ci aiuta a rispettare tutti gli altri principi. Il rispetto del DIP:
Questo è tutto. Abbiamo chiuso. Tutti i tutorial sui principi SOLID sono completi. Per me, personalmente, scoprire questi principi e realizzare progetti con loro in mente è stato un enorme cambiamento. Ho completamente cambiato il modo in cui penso al design e all'architettura e posso dire che da allora tutti i progetti su cui lavoro sono esponenzialmente più facili da gestire e capire.
Considero i principi SOLID uno dei concetti più essenziali della progettazione orientata agli oggetti. Questi concetti che devono guidarci a rendere il nostro codice migliore e la nostra vita di programmatori sono molto più facili. Il codice ben progettato è più semplice da comprendere per i programmatori. I computer sono intelligenti, possono capire il codice indipendentemente dalla sua complessità. D'altra parte, gli esseri umani hanno un numero limitato di cose che possono tenere nella loro mente attiva e concentrata. Più specificamente, il numero di tali cose è The Magical Number Seven, Plus o Minus Two.
Dovremmo sforzarci di avere il nostro codice strutturato attorno a questi numeri e ci sono diverse tecniche che ci aiutano a farlo. Funziona con un massimo di quattro righe di lunghezza (cinque con la linea di definizione inclusa) in modo che possano essere tutte subito inserite nella nostra mente. Le rientranze non superano cinque livelli. Classi con non più di nove metodi. Modelli di progettazione che di solito utilizzano un numero di cinque o nove classi. Il nostro design di alto livello negli schemi di cui sopra utilizza da quattro a cinque concetti. Esistono cinque principi SOLID, ognuno dei quali richiede da cinque a nove sotto-concetti / moduli / classi da esemplificare. La dimensione ideale di un gruppo di programmazione è tra cinque e nove. Il numero ideale di squadre in un'azienda è tra cinque e nove.
Come puoi vedere, il magico numero sette, più o meno due è tutto intorno a noi, quindi perché il tuo codice dovrebbe essere diverso?