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ΓΉ.