Oggi parliamo di alcuni degli argomenti più importanti nell'analisi e progettazione del software, i pattern di progettazione e i code smell, che sono i due lati della stessa medaglia.
I primi riguardano come progettare bene e i secondi come accorgersi di aver progettato male.
Partiamo dalla definizione di design pattern. Che cos'è? È una soluzione comune a un problema di progettazione ricorrente.
Questi differiscono da un altro tipo di pattern, che si chiamano pattern GRASP, con cui condividono parte del nome, ma questi ultimi sono principi, consigli.
mentre i design pattern si spingono oltre e arrivano fino allo schema delle classi
partiamo dalla classificazione GOF
prima di tutto per cosa sta GOF?
sta per Gang of Four che deriva dal fatto che i suoi ideatori erano quattro persone
si tratta di 23 pattern in totale
Però nel corso ne vedremo solamente sette. Come si classificano? Si classificano su due assi, per lo scopo e per il raggio d'azione.
per quanto riguarda lo scopo possono essere creazionali, strutturali o comportamentali
per quanto riguarda il raggio d'azione possono essere su classi oppure su oggetti
facciamo qualche esempio di combinazioni di questi due assi
abbiamo per esempio il factory method che è un creazionale su classi
abbiamo per esempio l'adapter che è un pattern strutturale
e abbiamo observer e strategy che sono due pattern comportamentali su oggetti
adesso soffermiamoci su questi ultimi due perché spesso all'inizio vengono confusi
Allora, partiamo dal definire questi due pattern, indicando quale problema risolvono e come lo risolvono.
Lo strategy risponde al problema di un insieme di algoritmi correlati e variabili e vogliamo poterli cambiare. Cambiare come? Scegliere a quale utilizzare.
Quindi come risolviamo questo problema?
Ogni algoritmo viene messo in una classe separata, tutte con un'interfaccia comune.
Il context tiene un riferimento alla strategia e la usa senza sapere quale sia,
Mentre il client può sostituirla a runtime con una strategia diversa della stessa interfaccia per sostituirla con una strategia diversa all'occorrenza.
Per quanto riguarda l'observer, il problema che si pone è quello di più oggetti che vogliono sapere quando un altro oggetto cambia stato.
Ognuno reagendo a modo proprio. Il publisher non deve conoscerli.
Prima di tutto vediamo chi è il publisher nel pattern observer. È l'oggetto che viene osservato dagli altri.
Come risolviamo questo problema?
Abbiamo un'interfaccia, che è il subscriber, e ogni subscriber si registra al publisher
in modo che possa essere avvisato quando succede qualcosa.
Quindi qual è la distinzione tra questi due pattern?
Lo strategy serve a scambiare un comportamento, quindi scegliere quale comportamento vogliamo applicare.
Observer serve a notificare un cambiamento a più interessati.
Cos'hanno in comune? Si basano entrambi su polymorphism.
e forniscono delle protected variations
vediamo anche un'altra coppia di pattern che vengono spesso confusi
che sono l'adapter e il facade
partiamo vedendo l'adapter
abbiamo due interfacce incompatibili che però hanno bisogno di parlarsi
Quindi cosa facciamo per permettere che comunichino tra loro?
Mettiamo in mezzo un oggetto adattatore che converte i messaggi che si devono scambiare.
Esistono in due forme, adapter di classe e adapter di oggetto.
Invece la facade si applica quando abbiamo un sottosistema disordinato e complicato.
Mettiamo davanti a questo sottosistema un punto di contatto unico che offre un'interfaccia semplificata in modo che chi deve interfacciarsi con questo sottosistema non debba sapere di tutto e di più oppure confondersi su cosa effettivamente deve chiamare.
La distinzione tra questi due pattern è che l'adapter converte un'interfaccia che c'è già, mentre facade semplifica l'accesso a un insieme di cose.
Vediamo che cosa risolvono.
L'adapter risolve un'incompatibilità, mentre facade risolve una complessità.
Al facade di solito si accede tramite un singleton e sostiene il low coupling aggiungendo un livello di indirection
Vediamo a questo punto che cos'è singleton e anche i suoi difetti
Prima di tutto cos'è?
è una sola istanza di una classe con un punto di accesso globale
cioè tutti gli oggetti che si interfacciano con essa
si interfacciano sempre con la stessa istanza
abbiamo un metodo statico getInstance
che ci restituisce l'istanza unica che è stata creata
e ha un costruttore privato
per negare la possibilità di crearne una ulteriore quando ne esiste già una.
Come si riconosce in UML?
Abbiamo solitamente un 1 in alto a destra nella classe.
Le due implementazioni possono essere lesi oppure non lesi.
Come si differenziano?
Quelle lazy vengono create solo quando servono, perciò se magari all'avvio del nostro software quel singleton non è necessario ma invece ne abbiamo bisogno solamente nel momento in cui una richiesta o una certa azione appunto richiede di usare questa istanza, la creiamo solo in quel momento.
invece possiamo avere quelle non lazy che vengono create subito anche se non sono necessarie in quel momento
quali sono i limiti? serve synchronized su getinstance oppure con più thread rischiamo di avere due o più istanze
non funziona bene nelle applicazioni distribuite perché comunque ogni macchina virtuale avrebbe la propria istanza
e quindi questi sono due grossi limiti che si applicano in situazioni particolari ma non proprio di nicchia
vediamo invece adesso i code smell
allora che cosa sono i code smell?
sono delle caratteristiche del codice che possono essere indizio di una cattiva pratica
Non si tratta necessariamente di bug, è proprio un cattivo odore, cioè qualcosa che non va ma che non è effettivamente un bug o un problema di per sé.
Che cosa si fa? Si fa refactoring per toglierli. In cosa consiste il refactoring? Consiste nel riscrivere il codice senza cambiarne il comportamento esterno a piccoli passi rieseguendo i test dopo ognuno. Quindi una cosa importantissima da tenere sempre a mente è che il refactoring non corregge i bug.
vediamo adesso le 5 famiglie di code smell
abbiamo i bloaters che sono codice sproporzionato
ad esempio long method, large class, long parameter list
poi abbiamo gli object orientation abusers
che è l'uso sbagliato dell'object oriented
che sono lo switch statement e il refused bequest
abbiamo poi i change preventers che sono parti troppo accoppiate che impediscono le modifiche
per esempio shotgun surgery
poi abbiamo i dispensables, cose che è meglio togliere
duplicated code, data class, comments
infine abbiamo i couplers che sono accoppiamento eccessivo
come ad esempio il code smell, feature envy
Per ognuno di questi il sintomo si può riassumere in una sola frase.
Per esempio il long method è un metodo con troppe righe e quindi è probabilmente una buona idea dividerlo in più metodi separati
che fanno una parte dell'esecuzione che sia, come dire, che abbia un senso proprio.
Poi, come possiamo definire refused bequest?
La sottoclasse non usa quello che eredita e quindi la gerarchia è sbagliata.
Poi abbiamo shotgun surgery.
Per una modifica piccola devi toccare tante classi diverse che crea grossi problemi perché comunque modificando tante classi si rischia di rompere qualcosa o di cambiare comportamenti e di non rendersene conto subito perché passano inosservati.
Le data class, semplicemente sono classi che sono solo contenitori di dati ma che non hanno logica e quindi non fanno niente
Infine vediamo per esempio il feature envy, che cos'è? È un metodo che usa più i dati di un altro oggetto che i propri
Vediamo delle coppie sintomo rimedio
switch statement
se puoi sfruttare il polimorfismo
replace conditional with polymorphism
è il metodo di refactoring adatto a risolvere questo problema
per quanto riguarda il feature envy
se il metodo sta chiaramente nel posto sbagliato
applichiamo il metodo di refactoring move method
che consiste semplicemente nel spostare il metodo nella classe più corretta
per quanto riguarda il long method il metodo di refactoring che possiamo applicare è extract method
se il problema sono le variabili locali invece replace temp with query oppure introduce parameter object
che sono tutti dei metodi di refactoring che si basano appunto su motivi diversi per cui il metodo sia lungo.
Quindi, riassumendo, la regola del 3. Si fa refactoring quando la stessa porzione è riusata almeno tre volte. Il copia e incolla è permesso una volta sola.
Gli altri momenti buoni, quando si aggiunge una funzionalità, quando si corregge un bug e durante una revisione del codice.
Si può fare refactoring anche in questi momenti.
Quindi, i pattern ci dicono come fare le cose bene, mentre i code smell come accorgerci di non averle fatte bene.
Questo chiude questa piccola lezione sui design pattern e i code smell.