Il problema analizzato nel pattern Polymorphism (e per motivare le scelte fatte) è più specificatamente un esempio del design pattern GoF strutturale Adapter.
Nome | Adapter |
---|---|
Problema | Come gestire interfacce incompatibili, o fornire un'interfaccia stabile a componenti simili ma con interfacce diverse? |
Soluzione | Convertire l'interfaccia originale di un componente in un'altra interfaccia, attraverso un oggetto adattatore intermedio. |
Prima di tutto chiariamo la formulazione del problema:
Per riassumere: il sistema POS deve supportare diversi tipi di servizi esterni prodotti da terzi, compresi i sistemi per la contabilità e per l'inventario, i servizi di autorizzazione ai pagamenti, i servizi per il calcolo delle imposte, ecc... Ciascuno di essi ha una propria API, diversa da quelle degli altri, che non può essere modificata poiché è fornita da terze parti.
Una soluzione consiste nell'aggiungere un livello di Indirection con oggetti che adattano le interfacce esterne (variabili) a un interfaccia compatibile utilizzata all'interno dell'applicazione.
La soluzione è illustrata nella figura sottostante.
Per esempio, l'interfaccia ITaxCalculatorAdapter è un interfaccia che è implementata dalle classi che sfruttano le API di terze parti, in particolare TaxMasterAdapter e GoodAdGoldTaxPro_Adapter.
Un modulo che ad un certo punto contiene un oggetto che fa riferimento a (definito come) ITaxCalculatorAdapter chiamando il metodo getTaxed(Sale), la richiesta di tale chiamate viene ricevuta dall'Adapter che "adatta" la richiesta all'interfaccia del componente istanziato.
public interface CalculationAdapter { // adapter
void calculation();
}
public class ClassAAdapter implements CalculationAdapter { // classe che implementa API
@Override
public void calculation() {
/* effettua richiesta verso l'interfaccia esterna che magari riceve le richieste
attraverso servizi particolari: HTTP, o altri (formato del server) */
}
}
public class ClassBAdapter implements CalculationAdapter { // classe che implementa API
@Override
public void calculation() {
/* effettua richiesta verso l'interfaccia esterna che magari riceve le richieste
attraverso servizi particolari: HTTP, o altri (formato del server) */
}
}
public class Main {
public static void main(String[] args) {
CalculationAdapter adapter1 = new ClassAAdapter();
// classA è definita con il tipo dell'adapter
CalculationAdapter adapter2 = new ClassBAdapter();
// classB è definita con il tipo dell'adapter
adapter1.calculation();
// quando viene chiamato calculation, la richiesta di chiamata (grazie al
// polimorfismo) è fatta all'adapter che chiama il metodo calculation()
// per la classe istanziata, in questo caso ClassA.
adapter2.calculation();
}
}
In generale, un adattatore riceve richieste dai suoi client, per esempio da un oggetto dello strato del dominio. Queste richieste sono nel "formato del client" dell'adattatore; per esempio un messaggio postSale(Sale) di IAccountingAdapater (vedi figura sopra). L'adattatore poi adatta una richiesta ricevuta in una richiesta nel "formato del server". Come si vede anche nell'esempio sopra un client può tranquillamente chiamare il metodo calculation(), poi l'adattatore esegue le meccaniche per adattare la chiamata all'interfaccia esterna (HTTP, XML, JSON, o altre meccaniche più complesse).
Dopo l'invio di una richiesta può seguire l'invio, da parte del server, di una risposta. La risposta arriva nel formato del server, viene gestita dall'adattatore che la "adatta" al formato del client.
Si noti che l'adattatore contiene la parola Adapter.