Java OOP: disaccoppiamento totale con protezione implicita
Java OOP: disaccoppiamento totale con protezione implicita
Come disaccoppiare due classi? Esaminiamo in questo articolo un utilizzo mirato delle interfacce come “facade”, rispetto alle classi contenenti l’implementazione della logica applicativa.
Il disaccoppiamento (decoupling) tra le classi consente una elevata scalabilità del software, consentendo di ridurre le dipendenze tra classi differenti. Un basso grado di accoppiamento tra le classi garantisce un impatto minimo delle future modifiche e una migliore redistribuzione delle responsabilità e riusabilità delle classi: il concetto di decoupling assume allora un ruolo di primaria importanza nella progettazione software, e può essere affrontato da vari punti di vista.
Se nell’articolo precedente (“disaccoppiamento lost-identity“) abbiamo introdotto il concetto ed una strategia di implementazione, qui cercheremo di sviluppare un altro tipo di approccio al problema. Per disaccoppiare le classi il metodo migliore consiste nell’utilizzare le interfacce, l’interfaccia costituisce un elemento facade fa cioè da proxy rispetto alla classe di utilizzo.
In realtà esistono almeno due livelli di accoppiamento:
accoppiamento tra classi: in tal caso le interfacce rappresentano una soluzione ottimale se non la migliore
accoppiamento tra metodi: il punto 1 deve tener conto del fatto che le classi sono accoppiate con i metodi della classe target
facciamo un esempio del punto 1, creiamo una interfaccia con un metodo di prova foo():
package prova;
public interface MyInterface {
public void foo();
}
e la classe che la implementa:
package prova;
public class Target implements MyInterface {
@Override
public void foo() {
// TODO...
}
infine una classe di utilizzo:
package prova;
public class Source {
private MyInterface ref;
public Source(MyInterface ref) {
super();
this.ref = ref;
}
}
Quello che segue è il corrispondente diagramma UML:
uml: disaccoppiamento tramite interfacce
e se volessimo modificare un metodo?
Le classi Target e Source sono disaccoppiate tra loro. Il problema però è che esiste ancora l’accoppiamento tra i metodi: supponiamo infatti di voler cambiare il nome del metodo da foo() a foo2() o, peggio ancora , di voler aggiungere (o eliminare) un metodo come nella figura seguente:
aggiunta di un metodo all'interfaccia
In sostanza non si può parlare ancora di disaccoppiamento totale.
Vediamo a questo punto una possibile soluzione al problema: creiamo prima di tutto una classe che elenca le attività (activity); ciascuna di esse rappresenta concettualmente un metodo:
package com.business;
public class ActivityDescription {
public final static String activity1 = "call foo()";
public final static String activity2 = "call foo1()&foo2()";
public final static String activity3 = "call foo3()";
public final static String activity4 = "call foo1()&foo2()";
public final static String activity5 = "call foo3()";
}
creiamo quindi la seguente interfaccia, che farà da “tramite”:
package com.business;
import java.util.HashMap;
public interface ActivityProxyInterface {
public HashMap
executeActivity(String activity, HashMap params);
}
Quest’ultima interfaccia prevede un unico metodo execute() che riceve il nome di una attività da eseguire e una HashMap che contiene i parametri di input dell’attività, mentre una ulteriore hashmap restituisce i parametri di output. Il metodo invocato quindi non si lega al tipo dei parametri sia di input che di output (si tratta di Object).
Ovviamente ci servirà una classe target che la implementa:
Il metodo principale sarà executeActivity() che, in funzione della attività da eseguire chiama uno o più metodi della classe stessa.
É interessante notare come i metodi della logica di business (qui simulati, ovviamente) abbiano tutti qualificatori private, quindi il metodo executeActivity() è l’unico visibile ed agisce da “dispatcher” per gli altri metodi.
Ora creiamo una ulteriore classe come quella precedente ma che implementa diversi metodi business sia come numero che come firma:/p>
a questo punto ci servirà un executor simile alla classe seguente:
package com.business;
import java.util.HashMap;
import java.util.Vector;
public class Executor {
private ActivityProxyInterface proxy;
private HashMap outParams = new HashMap();
public void setProxy(ActivityProxyInterface proxy) {
this.proxy = proxy;
}
// Attività 1
public void exec1() {
proxy.executeActivity(ActivityDescription.activity1, null);
proxy.executeActivity(ActivityDescription.activity2, null);
outParams = proxy.executeActivity(
ActivityDescription.activity3, null);
System.out.println((String) outParams.get(ActivityDescription.activity3));
}
// Attività 2
public void exec2() {
outParams = proxy.executeActivity(ActivityDescription.activity4, null);
Vector res = (Vector) outParams.get(ActivityDescription.activity4);
System.out.println(res.get(0));
proxy.executeActivity(ActivityDescription.activity5, null);
}
}
ed utilizzeremo per i test una classe come la seguente:
package com.business;
import java.util.HashMap;
import java.util.Vector;
public class Test {
public void execute(ActivityProxyInterface proxy) {
proxy.executeActivity(ActivityDescription.activity1, null);
proxy.executeActivity(ActivityDescription.activity2, null);
HashMap outParams = proxy.executeActivity(
ActivityDescription.activity3, null);
System.out.println(outParams.size());
System.out.println(outParams.containsKey(ActivityDescription.activity3));
System.out.println(outParams.get(ActivityDescription.activity3));
}
public static void main(String[] args) {
TargetClass tc = new TargetClass();
Executor executor = new Executor();
executor.setProxy(tc);
executor.exec1();
TargetClass2 tc2 = new TargetClass2();
executor.setProxy(tc2);
executor.exec2();
System.out.println(outParams.get(ActivityDescription.activity3));
}
}
eseguendo il codice otteremo:
esecuzione dell'executor
Cerchiamo ora di analizzare nel dettaglio il funzionamento dell’esempio fin qui costruito, partendo dallo schema UML che lo rappresenta:
UML: disaccoppiamento con executorUML: activity descriptor
verifichiamo il disaccoppiamento
Ciascuna classe Target esegue le attività descritte non i metodi, quindi l’accoppiamento tra i metodi non esiste, il disaccoppiamento riguarda anche i parametri di I/O inoltre allo stesso modo si possono gestire le eccezioni (un’unica eccezione con un riferimento indiretto nella gerarchia di ereditarietà a quella specifica, allo stesso modo di Spring DAO).
Facciamo una modifica al codice, supponiamo che la classe TargetClass2:
disaccoppiamento: modifiche ad un metodo
e ancora aggiungere:
disaccoppiamento: modifiche ad un metodo in base ad activity
mentre nella classe ActivityDescription potremmo procedere come evidenziato:
É interessante notare come le modifiche vanno sempre fatte in realtà su varie classi, però è facile osservare come sia possibile caricare il nome della attività da eseguire da un file esterno (ad esempio un file di properties), invece di scriverlo nel codice come nell’esempio.
Anche la gestione degli errori è qui più semplice: risulterebbe facile ignorare eventuali nomi di attività non corrispondenti ad implementazioni (perché errati, o facenti riferimento ad implementazioni da realizzare o eliminate), così come creare meccanismi di controllo sofisticato su questo genere di errori che non ci interessa trattare in questa sede.
considerazioni finali
In generale per come abbiamo realizzato il tutto l’architettura disaccoppia totalmente le classi, oltretutto i metodi di business sono tutti protetti, e ogni componente viene visto nello stesso modo:
un componente: executeActivity
Infine ciascuna attività può essere composta da chiamate a più metodi (ad esempio 3 dei quali solo il secondo restituisce un valore in return).
In questa catena di chiamate a metodi basta che uno solo tra di essi restituisca un valore per determinare un valore in uscita dell’attività.
Se vuoi aggiornamenti su Java OOP: disaccoppiamento totale con protezione implicita inserisci la tua email nel box qui sotto:
Compilando il presente form acconsento a ricevere le informazioni
relative ai servizi di cui alla presente pagina ai sensi
dell'informativa sulla privacy.
La tua iscrizione è andata a buon fine. Se vuoi ricevere informazioni personalizzate compila anche i
seguenti campi opzionali:
Compilando il presente form acconsento a ricevere le informazioni
relative ai servizi di cui alla presente pagina ai sensi
dell'informativa sulla privacy.
I Video di HTML.it
Carriera veloce con Android, l’esperienza di Frankie Sardo
Frankie Sardo dà alcuni consigli importanti agli sviluppatori e racconta come la sua passione per Android lo abbia portato a […]
In questo articolo impareremo a gestire gli URL (Uniform Resource Locator) attraverso le API messe a disposizione dal linguaggio di programmazione Java.
Java 13: In questo articolo andiamo a presentare le nuove caratteristiche introdotte dalla release elencandole in base al codice JEP che le identifica.
Impariamo a sviluppare applicazioni Java per i sistemi Embedded, piattaforme che a differenza delle tradizionali soluzioni general purpose vengono progettate per svolgere compiti specifici. Nel corso delle guida verranno fornite le nozioni necessarie per installare Oracle Java SE Embedded, scegliere il profilo e la JVM da utilizzare, configurare una JRE personalizzata, creare una prima App ed effettuarne il deploy