In questo articolo proseguiamo con l'approfondimento "pratico" di temi legati alle best practice OOP in ambito java, stavolta dedicandoci all'utilizzo del Template Method Pattern.
I Pattern sono come sappiamo delle linee guida da seguire per ottenere codice di qualità, ma il loro utilizzo non è prescrittivo e possono pertanto essere utilizzati in diversi modi, e con maggiore o minore difficoltà. Nel nostro caso utilizzeremo Il Template Method "simulando" un altro pattern esistente, cioè il Decorator Pattern, per aggirare alcuni aspetti negativi e difficoltosi che possono presentarsi nell'utilizzo di quest'ultimo. Ma procediamo con ordine.
Esempio con il Decorator Pattern
Facciamo prima di tutto un semplice esempio di Decorator Pattern, apriamo MyEclipse e creiamo un java project che chiamiamo ProjectTMP
, creiamo un package com.decorator
creiamo quindi la nostra interfaccia, e la relativa classe di implementazione:
package com.decorator;
public interface IComponent {
public void doStuff();
}
package com.decorator;
public class Component implements IComponent {
public void doStuff() {
System.out.println("Do Suff");
}
}
Il pattern Decorator ha come finalità l'aggiunta una (o più) funzionalità a livello di metodo ad una classe già esistente: dovremmo quindi preoccuparci di definire una opportuna interfaccia (sufficientemente generica):
package com.decorator;
public interface Decorator extends IComponent {
public void addedBehavior();
}
ed implementare una classe su di essa che riutilizzi la classe precedente:
package com.decorator;
public class ConcreteDecorator implements Decorator {
IComponent component;
public ConcreteDecorator(IComponent component) {
super();
this.component = component;
}
public void addedBehavior() {
System.out.println("Decorator does some stuff too");
}
public void doStuff() {
component.doStuff();
addedBehavior();
}
}
Aggiungiamo per completezza una piccola classe di test:
package com.decorator;
package com.decorator;
public class Client {
public static void main(String[] args) {
IComponent comp = new Component();
Decorator decorator = new ConcreteDecorator(comp);
decorator.doStuff();
}
}
Prima di fare qualche considerazione su quanto scritto finora, vediamo l'output prodotto dalla console:
É interessante fare un po' di riflessioni sul codice appena prodotto:
- la classe
Decorator
non può esistere senzaComponent
(non è vero l'opposto). Consideriamo inoltre la necessità di istanziare il riferimento alComponent
all'interno diDecorator
: che venga fatto o meno attraverso l'Inversion of Control (così da limitare l'accoppiamento della classe), va comunque fatto. - C'è un basso livello di coesione nelle due classi, in sostanza
Decorator
aggiunge funzionalità alla
classeComponent
, e dovremo quindi pensare aDecorator
come la classe cui fare riferimento, invece che
Component
. D'altro canto se pensiamo a Decorator come una "evoluzione" diComponent
, ci ritroviamo con la situazione paraddosale di due classi che ne rappresentano una! Con metodo molto semplice l'aggiunta di funzionalità comporterebbe l'aggiunta di altri metodi e classi.Tanto per proseguire nella nostra idea, cercando di generalizzarla e di aggirare i problemi emersi, possiamo pensare di utilizzare un altro pattern, il Template Method Pattern.
Rifattorizziamo l'esempio con il Template Method Pattern
Creiamo un altro package e chiamiamolo
com.tmpatternRev
. Senza fare riferimento in maniera precisa all'esempio precedente, creiamo la seguente classe:package com.tmpatternRev; public abstract class Clazz1 { protected abstract void foo1(); public void executeBusinessLogic() { System.out.println("executeBusinessLogic Clazz1"); foo1(); } }
Quindi sfruttiamo il Template Method Pattern creando una clas che estende la precedente in questo modo:
package com.tmpatternRev; public abstract class Clazz2 extends Clazz1 { protected void foo1() { System.out.println("called foo1() from Clazz1!"); } protected abstract void foo2(); public void executeBusinessLogic() { super.executeBusinessLogic(); System.out.println("executeBusinessLogic Clazz2"); foo2(); } }
Facciamo caso a cosa succede:
Clazz1
ha un metodo che ritroveremo in tutte le classi figlie (executeBusinessLogic()
), il quale stampa il messaggioexecuteBusinessLogic Clazz1
, stampa e richiama il metodo abstractfoo1()
, la cui implementazione è fornita dalla classe figliaClazz2
.Quest'ultima a sua volta fa la stessa cosa, solo che nel metodo
executeBusinessLogic()
richiama
il corrispondente della superclasse e vi aggiunge un metodo rappresentato dafoo2()
. Questa infineClazz3
:package com.tmpatternRev; public abstract class Clazz3 extends Clazz2{ protected void foo2() { System.out.println("called foo2() from Clazz2!"); } public void executeBusinessLogic() { super.executeBusinessLogic(); System.out.println("executeBusinessLogic Clazz3"); } }
Aggiungiamo infine la classe di test:
package com.tmpatternRev; public class Test { public static void main(String[] args) { BusinessClass bc = new BusinessClass(); bc.executeBusinessLogic(); } }
Abbiamo raggiunto il nostro scopo e migliorato un po' l'implementazione.