Nei precedenti articoli sulle strategie utili a migliorare il disaccoppiamento (Disaccoppiamento Lost Identity, Disaccoppiamento totale con Protezione Implicita) abbiamo visto come esistano diversi fattori che determinano l’accoppiamento tra unità software: in particolare abbiamo evidenziato come le classi in java e addirittura le firme dei metodi, siano fattori che possono promuovere una politica di sviluppo software poco propensa al disaccoppiamento tra le classi.
Questi fattori vanno contro il principio OCP (Open Closed Principle) secondo il quale una classe o più in generale un software, dovrebbe essere aperto alle estensioni e chiuso alle modifiche.
Possiamo quindi tranquillamente affermare che lo scambio di messaggi sia anch'esso un fattore che accentua l'accoppiamento tra classi. Spesso poi in implementazioni di uso comune quando una classe A chiama un metodo su una Classe B quest'ultima ha necessità di capire la tipologia del messaggio ricevuto e dieffettuare un casting in locale.
Questo può dunque essere visto come elemento accoppiante, anche se in realtà non fa parte di questo contesto.
Partiamo da un presupposto di base e che cioè non necessariamente una classe Source
per richiamare un metodo sulla classe Target
al quale passare un parametro debba necessariamente farlo attraverso quel metodo. Cioè più semplicemente, deve esistere un modo per far si che una classe Source
richiami un metodo printMessage(String message)
su una classe target senza inviargli message
.
La nostra tesi è che se questo fosse possibile si otterrebbe un ulteriore grado di disaccoppiamento.
Per il casting in locale questo è un tema che riprenderemo a fine articolo e che in qualche modo verrà risolto specializzando i metodi, come vedremo in seguito.
Creiamo un progetto java e chiamiamolo DecouplingCommunication
creiamo quindi la seguente classe nel relativo package:
package com.sperimental;
import java.util.Hashtable;
public class DataShared {
private Hashtable queue = new Hashtable();
public String getStringProperty(String source, String key) {
return (String) queue.get(key);
}
public void setProperty(String source , String key, Object prop) {
queue.put(key, prop);
}
}
DataShared è una classe che utilizzeremo per il transito del messaggio tra due classi: Source
e Target
. Creiamo la seguente interfaccia:
package com.sperimental;
public interface PrinterInterface {
public void printMessage();
}
E le due classi Source
e Target
:
package com.sperimental;
public class Source {
private DataShared ds;
private PrinterInterface pi;
private Source() {
super();
}
public Source(DataShared ds,PrinterInterface pi) {
super();
this.ds = ds;
this.pi = pi;
}
public void foo() {
ds.setProperty(this.getClass().getName(), "key Message4Target","messaggio da Source per Target!");
pi.printMessage();
}
}
package com.sperimental;
public class Target implements PrinterInterface {
private DataShared ds;
private Target() {
super();
}
public Target(DataShared ds) {
super();
this.ds = ds;
}
public void printMessage() {
String msg= ds.getStringProperty(this.getClass().getName(), "key Message4Target");
System.out.println("messaggio ricevuto:" + msg);
}
}
Cerchiamo ora di fare un po' di chiarezza analizzando il codice:
public void foo() {
ds.setProperty(
this.getClass().getName(),
"key Message4Target",
"messaggio da Source per Target!"
);
pi.printMessage();
}
Utilizza una istanza condivisa di DataShared
per definire il valore della property da inviare alla classe Target
. In caso di memorizzazione il tipo di dato che viene memorizzato da DataShared
non è specificato: il metodo corrispondente in DataShared
accetta come parametro da memorizzare un valore di tipo Object
:
public class DataShared {
private Hashtable queue = new Hashtable();
public void setProperty(String source , String key, Object prop) {
queue.put(key, prop);
}
}
La classe Target
nel metodo printMessage()
:
public void printMessage() {
String msg= ds.getStringProperty(
this.getClass().getName(),
"key Message4Target"
);
System.out.println("messaggio ricevuto:" + msg);
}
recupera la property e la stampa.
Facciam oa questo punto alcune considerazioni:
- Le classi
Source
eTarget
non hanno accoppiamento legato ai messaggi; "normalmente" quando
Source
richiama il metodoprintMessage()
diTarget
dovrebbe passargli il messaggio che viene invece memorizzato nelDataShared
per il transito. - Quando viene recuperato il messaggio da
Target
nel metodoprintMessage()
non è necessario un casting esplicito perchè il metodogetStringproperty()
’ è specializzato. DataShared
potrebbe anche tenere traccia dei messaggi e del loro flusso.Source
eTarget
sono tra loro disaccoppiate tramite interfaccia e devono entrame condividere la stessa istanza diDataShared
.- Possono essere considerate tranquillamente precauzioni per la Thread-Safety.
Questo articolo approfondisce una tematica che riguarda i limiti della metodologia OOP: aldilà dell'aspetto pratico ci sarà utile come introduzione al prossimo articolo di questa serie di approfondimenti su Java e OOP, dedicato alla Programmazione Data Centric.