Refactoring con Netbeans (parte 2)
Abbiamo già visto le funzioni di refactoring di base. Ora ne vediamo alcune avanzate e legate alla gestione dell’ereditarietà. Il problema che ho in mente è che quando inizio a scrivere una classe Java spesso non so esattamente dove andrò a parare. Magari so che successivamente scriverò qualche sottoclasse e che magari astrarrò un’interfaccia, ma non posso immaginare i dettagli. Per esempio, non so con certezza se un certo metodo x() andrà posizionato a livello dell’interfaccia, della classe o di una delle sottoclassi. E’ qui che il refactoring torna utile permettendo ripensamenti e correzioni.
Prendiamo come esempio un sistema di gestione progetti utilizzato internamente da un’azienda. Definisco un progetto in termini del suo budget e dipartimento. Inoltre è necessario che il progetto possa produrre un semplice report testuale. La mia prima idea è di scrivere una classe così:
1 public class Progetto {
2 private double budget;
3 private String dipartimento;
4
5 public Progetto() {
6 setBudget(0.0);
7 setDipartimento("generale");
8 }
9
10 public double getBudget() {
11 return budget;
12 }
13
14 public void setBudget(double budget) {
15 this.budget = budget;
16 }
17
18 public String getDipartimento() {
19 return dipartimento;
20 }
21
22 public void setDipartimento(String dipartimento) {
23 this.dipartimento = dipartimento;
24 }
25
26 public String produciReport() {
27 return "tutto bene";
28 }
29 }
Push down
… ma ovviamente non appena la scrivo, i requisiti cambiano. Scopro che quello che ho modellato è un progetto interno, ma esistono anche i progetti esterni. Ciò che distingue il progetto esterno è che non ha un dipartimento, ma ha un cliente ed un fornitore.
Devo cambiare qualcosa. Potrei aggiungere alla classe i campi ‘cliente’ e ‘fornitore’ oltre a ‘budget’ e ‘dipartimento’ che ha già. Ma così verrebbe una classe progetto che è un misto di progetto interno ed esterno. Meglio usare l’ereditarietà e creare due sottoclassi ProgettoInterno e ProgettoEsterno.
Iniziamo creando le classi ProgettoInterno e ProgettoEsterno molto seplici e minimali:
1 public class ProgettoInterno extends Progetto {
2 public ProgettoInterno() {
3 super();
4 }
5 }
1 public class ProgettoEsterno extends Progetto {
2 public ProgettoEsterno() {
3 super();
4 }
5 }
Ora vorrei spostare tutto ciò che riguarda il dipartimento (quindi la variabile ‘dipartimento’ e i suoi metodi get/set) da Progetto a ProgettoInterno. Faccio click col tasto destro sulla classe Progetto e dal menu seleziono la funzione di refactoring “Push Down”. Comparirà una finestra in cui posso selezionare gli elementi da spostare. Notare che seleziono anche l’opzione “preview all changes” che mi servirà.

Premo “Next” e mi viene proposto un riassunto di quello che Netbeans sta per fare: in sostanza cancellerà dipartimento, getDipartimento() e setDipartimento() da Progetto e li ricreerà sia in ProgettoInterno che in ProgettoEsterno. Noto però che il cambiamento deve riguardare solo Progetto e ProgettoInterno e quindi de-seleziono ProgettoEsterno:

La fattorizzazione è quasi perfetta. Servono alcuni aggiustamenti a mano dei costruttori e devo anche rendere la classe Progetto astratta. Le classi modificate risulteranno come segue:
1 public abstract class Progetto {
2 private double budget;
3
4 public Progetto() {
5 setBudget(0.0);
6 }
7
8 public double getBudget() {
9 return budget;
10 }
11
12 public void setBudget(double budget) {
13 this.budget = budget;
14 }
15
16 public String produciReport() {
17 return "tutto bene";
18 }
19 }
1 public class ProgettoInterno extends Progetto {
2 public ProgettoInterno() {
3 super();
4 setDipartimento("generale");
5 }
6
7 private String dipartimento;
8
9 public String getDipartimento() {
10 return dipartimento;
11 }
12
13 public void setDipartimento(String dipartimento) {
14 this.dipartimento = dipartimento;
15 }
16 }
Bisogna ancora mettere a posto ProgettoEsterno perché possa gestire i campi ‘cliente’ e ‘fornitore’, ma questo è molto semplice e non richiede funzioni particolari. Il risultato sarà il seguente.
1 public class ProgettoEsterno extends Progetto {
2 private String cliente;
3 private String fornitore;
4
5 public ProgettoEsterno() {
6 }
7
8 public String getCliente() {
9 return cliente;
10 }
11
12 public void setCliente(String cliente) {
13 this.cliente = cliente;
14 }
15
16 public String getFornitore() {
17 return fornitore;
18 }
19
20 public void setFornitore(String fornitore) {
21 this.fornitore = fornitore;
22 }
23 }
Simile alla funzione “push down”, esiste anche una funzione “pull up” che sposta variabili e metodi da una classe verso la sua classe di base. La lascio come esercizio per il lettore ![]()
Ho preferito parlare di “push down” perché ha un complicazione in più: una classe può avere più derivate e a volte mi interessa spostare elementi dalla classe verso alcune delle sue derivate ma non altre, come appunto visto nell’esempio di Progetto.
Extract Interface
Ok, nuovo cambiamento dei requisiti. Adesso oltre a progetti dobbiamo anche gestire pratiche. La pratica ha un richiedente, uno stato approvazione e può produrre un semplice report. Una semplice implementazione è la seguente:
1 public class Pratica {
2 private final String richiedente;
3 private boolean approvata;
4
5 public Pratica(String richiedente) {
6 this.richiedente = richiedente;
7 }
8
9 public String getRichiedente() {
10 return richiedente;
11 }
12
13 public boolean isApprovata() {
14 return approvata;
15 }
16
17 public void setApprovata(boolean approvata) {
18 this.approvata = approvata;
19 }
20
21 public String produciReport() {
22 return isApprovata()
23 ? "approvata"
24 : "ancora no";
25 }
26 }
Noto che la funzione produciReport() è identica, come signature, a quella presente in Progetto. Se Progetto e Pratica avessero un’interfaccia comune contenente questo metodo, potrei eseguire report sulle due classi in modo trasparente.
Netbeans dovrebbe offrire una funzione di refactoring proprio per questo. “Fattorizzare” vuol dire proprio estrarre la parte comune da due classi. Ovvio? No. In effetti Netbeans NON ha una funzione che estrae un’interfaccia a partire da DUE classi. O almeno io non ci sono riuscito. Se mi sbaglio, fatemi sapere.
Quello che ha è una funzione per estrarre un’interfaccia a partire da UNA classe. Quindi scelgo una delle classi fra Progetto e Pratica (scelgo Progetto) e da essa estraggo un’interfaccia.
La funzione si chiama “Extract Interface” e si presenta così:

E’ necessario specificare un nome per l’interfaccia da creare (in questo caso “Reportable”) e quali metodi ne farranno parte. Premendo “Next” verrà automaticamente creata l’interfaccia come segue.
1 public interface Reportable {
2 String produciReport();
3
4 }
La classe Progetto verrà modificata automaticamente con “implements Reportable”. Per quanto riguarda Pratica, questa aggiunta è da fare a mano.
Simile a “Extract Interface”, esiste una funzione “Extract Superclass” che è una via di mezzo fra “Extract Interface” e “Pull Up”. La lascio come esercizio per il lettore
Ci mancano ancora un po’ di funzioni di refactoring da vedere: argomento per la prossima puntata…