Questi principi seguono quanto indicato nellโottimo libro Head First Design Patterns (ISBN-13: 978-0596007126).
1. Separa le parti che cambiano da quelle che rimangono costanti
Durante il design di unโapplicazione, รจ necesaario essere in grado di individuare le parti del codice che variano (o potrebbero variare in futuro) ed incapsularle in modo che siano indipendenti da quelle che tendono a rimanere costanti. In questo modo, quando devo effettuare una modifica a tali parti, queste modifiche saranno molto piรน semplici e dovrรฒ andare a modificare solo il codice che effettivamente cambia.
2. Scegli la composizione invece dellโereditarietร
Questa regola si puรฒ riassumere come: HAS-A รจ meglio di IS-A. Quando collego due classi insieme, inserendo una come attributo dellโaltra sto operando una composizione (HAS-A). Tipicamente fare in modo che un oggetto abbia una caratteristica esterna ad esso รจ meglio di creare รจ un oggetto intrinsicamente possiede tale caratteristica. Lโesempio piรน semplice รจ la classe animale. Il comportamento dellโanimale puรฒ sia essere scritto come metodo della classe ma รจ una pratica di buona programmazione fare sรฌ che il comportamento sia un insieme di oggetti esterni allโanimale ma che viene posto in composizione con questo ultimo. Questo ha il vantaggio, oltre di rendere il codice piรน manutenibile, che posso cambiare il comportamento a runtime, rendono cosรฌ il codice estremamente flessibile.
3.Programma ad interfacce, non a implementazioni
In questo caso il concetto di interfaccia non significa solo lโInterface di Java o C#, ma significa utilizzare il polimorfismo utilizzando superclassi (astratte o interfacce) al posto di classi concrete. Vediamo un esempio: Il seguente codice segue la programmazione per implementazione:
Dog d = new Dog();
d.bark();
Come si puรฒ notare questo codice รจ estremamente debole, in quanto se voglio cambiare animale dovrei cambiare entrambe le righe, e devo cambiarne una invece se voglio cambiare il verso.
Questo perchรจ aver dichiarato d
come Dog
ci obbliga ad utilizzare solo i metodi della classe concreta
Il codice seguente invece segue la programmazione a interfacce:
Animal a = new Dog();
a.makeSound();
in quanto io so che รจ un Dog
, ma lo dichiaro con la sua superclasse astratta Animal
che implementerร il metodo astratto makeSound()
. In questo modo qualora volessi cambiare animale, cambierei solo il nome dellโistanza, facendo rimanere invece costante tutto il resto.
Ancora meglio รจ astrarre ulteriormente evitando lโistanziazione di una classe concreta hard-code, utilizzando un metodo che assegna lโistanza della variabile a runtime.
Animal a = getAnimal();
a.makeSound();
in questo caso il grado di astrazione รจ massimo: noi vogliamo che sia un animale e che sappia emettere suoni, poi che animale sia e come fa ad emetterli non รจ un problema di questa classe.
4.Principio dellโaccoppiamento minimo
Due oggetti si dicono minimamente accoppiati quando sono in grado di interagire fra di loro pur conoscendo il minimo possibile della struttura reciproca. Questo significa che
- sono flessibili allโaggiunta o alla rimozione di oggetti correlati
- per modificare un oggetto non devo modificare anche lโoggetto a questo correlato
- possiamo riusare un oggetto o lโaltro in maniera indipendente tra di loro
Strutturare il codice secondo questo principio ci permette di costruire sistemi OO flessibili che possono facilmente gestire il cambiamento reciproco.
5. Le classi devono essere aperte allโestensione ma chiuse alla modifica
Un buon design deve creare delle classi che sono:
- aperte allโestensione: gli oggetti devono essere fatti in modo che sia il piรน facile estenderli al fine di incorporare una nuova funzionalitร
- chiusi alla modifica: una volta che un oggetto funziona correttamente senza bug, ogni sua modifica rischia di immetterne di nuovi. Un buon design deve fare in modo che le modifiche al codice esistente su una funzionalitร vecchia siano ridotte al minimo
Il nostro obiettivo รจ qunidi quello di crare classi che siano facilmente estendibili al fine di incorporare un nuovo comportamento, senza che questo comportui la modifica del codice pre-esistente. Qualora vi sia la necessitร di cambiare una funzionalitร vecchia, il codice deve essere sufficentemente flessibile da poter incoporare la modifica con il minor numero possibile di modifiche, ma solo aggiunte. Attenzione perรฒ, usare questo principio dovunque potrebbe portare ad un aumento di complessitร non necessario e a del codice difficile da comprendere. Scegliere con cura le parti gli oggettia cui applicare questo principio.
6. Dipendi dalle classi astratte, non concrete
Questo principio รจ anche detto il Dependency Inversion Principle e significa che i componenti ad alto livello non devono mai dipendere dai componenti a basso livello e nemmeno il contrario. Infatti entrambi devono dipendere da componenti astratti. Un componente si dice ad alto livello quando il suo comportamento รจ definito in termini di altri componenti a basso livello. Le seguenti linee guida aiutano a strutturare bene il codice secondo questo principio:
- Nessuna variabile deve essere istanziata con lโoperatore
new
. Meglio usare una factory! - Le classi concrete non dovrebbero avere figli. Se derivo da una classe concreta, significa che dipendo da una classe concreta. Eโ sempre meglio dipendere da astrazioni.
- Non si dovrebbe mai eseguire un override di un metodo implementato dalla propria superclasse: se per poter eseguire il mio codice devo eseguire un override dalla superclasse, significa che questa non era unโastrazione corretta. I metodi implementati nelle superclassi dovrebbero essere condivisi fra tutte le sottoclassi senza mai cambiare.
7. Principio della conoscenza minima
Il principio di minima conoscenza ci obbliga a sviluppare codice in cui gli oggetti comunicano solo con i loro โamici viciniโ. In poche parole, ogni oggetto deve comunicare con il minor numero possibile di oggetti diversi e quindi possiede la minima conoscenza possibile del sistema. Se cosรฌ non fosse avremmo molte classi tutte correlate fra di loro e il cambiamento di una porterebbe al cambiamento di molte altre. Lโesempio classico di design errato sono metodi concatenati come il seguente:
a.getB().getC().getD()
Tipicamente ogni oggetto dovrebbe invocare solo metodi che appartengono a:
- lโoggetto stesso;
- oggetti passati come parametro ad un metodo dellโoggetto
- oggetti creati o istanziati da un suo metodo
- un componente dellโoggetto (relazione HAS-A)
8. Il principio di Hollywood
Il principio di Hollywood si puรฒ riassumere con la seguente frase:
Non chiamarmi, ti chiamo io
Questo principio ci aiuta ad evitare lโanello di dipendenze, per cui se una classe di alto livello dipende da un componenete di basso livello il quale dipende da uno di alto livello, ho una catena di componenti che rendono il sistema complesso da analizzare.
Nel principio di Hollywood permettiamo ai componenti di basso livello di agganciarsi a dei componenti di alto livello, ma saranno loro a decidere quando questi sono necessari e come (che spiega la frase โnon chiamarmi, ti chiamo ioโ).
Esempi di pattern che seguono questo principio sono il Factory
, lโObserver
e il Template
.
9.Principio di singola responsabilitร
Un modo alternativo per definire questo principio รจ: una classe deve avere un solo motivo per cambiare. Il principio afferma che รจ necessario assegnare ad ogni classe una ed una sola responsabilitร , nel senso che si deve occupare solo di una cosa, unโazione o un oggetto. In un sistema ben strutturato, al momento del cambiamento, ogni oggetto cambia per solo un aspetto, non di piรน.