Trascrivo qui quello che ho scoperto finora dalla mia esplorazione del mondo Java a riguardo la necessità che aveva riscontrato all'inizio di questo thread - utilizzare quello che nel mondo Microsoft .net viene chiamato delegate o in C è il passaggio del ref di una funzione come parametro.
Io ho testato quattro metodi (ho tralasciato il metodo del passaggio dell'istanza di classe):
- Reflection
- Interface
- Pattern Observer
- Event con Listener
Con la reflection il tutto si risolve con poche righe di codice:
codice:
import java.lang.reflect.Method;
public class ClasseA {
public void toConsole(String str) {
System.out.println(str + " dalla ClasseA");
}
public void TestDelegate1() {
Method m1;
try {
m1 = ClasseA.class.getMethod("toConsole",
new Class[]{String.class});
ClasseB d11 = new ClasseB();
d11.TestReflection(this, m1);
} catch (NoSuchMethodException | SecurityException e) {
}
}
}
/******************************************/
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ClasseB {
public void TestReflection(Object instanceClass, Method method) {
try {
method.invoke(instanceClass, new Object[]{"Stringa da ClasseB"});
} catch (IllegalAccessException |
IllegalArgumentException |
InvocationTargetException ex) {
}
}
}
Richiamando la funzione TestDelegate1 in ClasseA vengono passati come parametro il riferimento a questa classe e al metodo toConsole al metodo della seconda classe che richiama il metodo passato con il parametro stringa voluto. Una volta lanciato si ottiene questo output:
codice:
Stringa da ClasseB dalla ClasseA
Se posso dare un parere del tutto personale questa tecnica è molto personalizzabile e apre la strada a qualsiasi implementazione, ma di contro c'è la difficoltà nella lettura codice (e conseguente difficile manutenzione anche da utenti esterni) e fragilità di tutta la struttura perché come riferimento alla funzione si può passare qualsiasi metodo con signature differente e solo runtime si potrà vedere il conseguente errore.
Con l'uso delle interfacce il tutto è più semplice:
codice:
interface IDelegate {
void toConsole(String msg);
}
public class ClasseA implements IDelegate {
@Override
public void toConsole(String str) {
System.out.println(str + " dalla ClasseA");
}
public void TestDelegate1() {
ClasseB d11=new ClasseB();
d11.TestInterface(this);
}
}
/*********************************************/
public class ClasseB {
void TestInterface(ClasseA interfaccia) {
interfaccia.toConsole("Stringa da Classe B");
}
}
Come l'esempio sopra, quando viene richiamato il metodo TestDelegate1 viene visualizzato questo output:
codice:
Stringa da Classe B dalla ClasseA
Qui il codice è molto più leggibile del precedente e l'uso delle interfacce non preclude l'ereditarietà da altre classi o l'implementazione di altre interfacce. Tra tutti i metodo qui esposti è probabilmente il metodo più semplice da implementare.
Ecco il codice che utilizza la terza tecnica: il pattern observer:
codice:
import java.util.Observable;
import java.util.Observer;
public class ClasseA implements Observer {
public void TestDelegate1() {
ClasseB d11 = new ClasseB();
d11.addObserver(this);
d11.TestPattern();
}
@Override
public void update(Observable o, Object arg) {
System.out.println(arg.toString() + " dalla ClasseA");
}
}
/*********************************************/
import java.util.Observable;
public class ClasseB extends Observable {
public void TestPattern() {
setChanged();
notifyObservers("Stringa da Classe B");
}
}
Per l'implementazione di questa tecnica ho utilizzato la classe Observable e l'interfaccia Observer presenti nel package Java.util. In questo caso la ClasseA che rimane in attesa deve ereditare dalla classe Observable, quindi deve essere aggiunta anche il metodo update che sarà richiamato nel caso di esempio qui sopra dall'altra classe. ClasseB, che deve richiamare il metodo, deve implementare l'interfaccia Observer e ci mette a disposizione nella nostra classe due metodi aggiuntivi:
- setChanged()
- notifyObservers(Object)
Utilizzando come nell'esempio qui sopra possiamo passare un oggetto alla classe in attesa (nel mio esempio passo una banale stringa).
Il vantaggio di questa tecnica è che possiamo attaccare anche più classi che saranno tutte richiamate dall'observer:
codice:
ClasseB d11 = new ClasseB();
ClasseA a1 = new ClasseA();
ClasseA a2 = new ClasseA();
ClasseA a3 = new ClasseA();
ClasseA a4 = new ClasseA();
ClasseA a5 = new ClasseA();
d11.addObserver(a1);
d11.addObserver(a2);
d11.addObserver(a3);
d11.addObserver(a4);
d11.addObserver(a5);
Lo svantaggio di questo approccio è nella necessità di ereditare dalla classe Observable che preclude qualsiasi altra ereditarietà per questa classe. Se non si ha questa necessità questa è la tecnica più flessibile e personalmente la ritendo la migliore da utilizzare.
L'ultimo metodo presa in considerazione è Event con Listener è probabilmente la più complessa da implementare ed è la classica tecnica che io definisco copia&incolla perché è impossibile da ricordare e implementare a memoria
Per implementare questa tecnica si devono creare due nuove classi. La prima la si deve ereditare dalla classe EventObject:
codice:
import java.util.EventObject;
public class MyEvent extends EventObject {
String _message;
public String getMessage()
{
return _message;
}
public MyEvent(Object source, String msg) {
super(source);
_message=msg;
}
}
In questo caso ho aggiunto anche una funziona aggiuntiva per avere il contenuto del messaggio, stringa che dovrà transitare dalla classeB alla classeA.
La seconda classe da implementare è per il Listener:
codice:
import java.util.EventListener;
public interface MyEventListener extends EventListener {
public void myEventOccurred(MyEvent evt);
}
In questo caso non mi serve nessuna personalizzazione e lascio tutto com'è. Infine ecco le due classi che utilizzano gli eventi per il messaggio:
codice:
public class ClasseA implements MyEventListener {
public void TestDelegate1() {
ClasseB d11 = new ClasseB();
d11.addMyEventListener(this);
d11.TestEvent();
}
@Override
public void myEventOccurred(MyEvent evt) {
System.out.println(evt.getMessage() + " dalla ClasseA");
}
}
/*******************************************/
public class ClasseB {
protected javax.swing.event.EventListenerList listenerList =
new javax.swing.event.EventListenerList();
public void addMyEventListener(MyEventListener listener) {
listenerList.add(MyEventListener.class, listener);
}
public void removeMyEventListener(MyEventListener listener) {
listenerList.remove(MyEventListener.class, listener);
}
void fireMyEvent(MyEvent evt) {
Object[] listeners = listenerList.getListenerList();
for (int iii = 0; iii < listeners.length; iii += 2) {
if (listeners[iii] == MyEventListener.class) {
((MyEventListener) listeners[iii + 1]).myEventOccurred(evt);
}
}
}
public void TestEvent() {
this.fireMyEvent(new MyEvent(this, "Stringa da Classe B"));
}
}
Il risultato è quello ormai noto:
codice:
Stringa da Classe B dalla ClasseA
Nella ClasseA non ho fatto altro che implementare l'interfaccia creata poco sopra MyEventListener e inserito il metodo che sarà richiamato quando sarà avviato l'evento: myEventOccurred. ClasseB ora deve implementare tutta la logica per la gestione dei Listener. Per la logica di funzionamento rimando ai link in calce a questo post.
Questa tecnica non ha limiti e limitazioni visto che si devono avere solo due accortezze: implementare una interfaccia alla classe e la scrittura di funzioni personalizzate all'interno della seconda classe per la gestione dei Listener.
Trascrivo qui tutto questo per avere una mia nota personale e per i neofiti che un giorno potrebbero riscontrare la stessa necessità. Naturalmente tutto quanto esposto non dev'essere inteso completo (non ho una conoscienza di Java così approfondita). Ogni critica costruttiva è ben accetta. Infine ringrazio ancora LeleFT e Andrea1979 per gli spunti dati.
Link di riferimento da dove ho studiato/preso le tecniche esposte: