Maggiori informazioni sul problema
Per prima cosa, simuleremo il comportamento del vecchio sistema. Supponiamo che generi scuse per essere in ritardo al lavoro oa scuola. Per fare questo, ha un'interfacciaExcuse
che ha generateExcuse()
e metodi. likeExcuse()
dislikeExcuse()
public interface Excuse {
String generateExcuse();
void likeExcuse(String excuse);
void dislikeExcuse(String excuse);
}
La WorkExcuse
classe implementa questa interfaccia:
public class WorkExcuse implements Excuse {
private String[] excuses = {"in an incredible confluence of circumstances, I ran out of hot water and had to wait until sunlight, focused using a magnifying glass, heated a mug of water so that I could wash.",
"the artificial intelligence in my alarm clock failed me, waking me up an hour earlier than normal. Because it is winter, I thought it was still nighttime and I fell back asleep. Everything after that is a bit hazy.",
"my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia."};
private String [] apologies = {"This will not happen again, of course. I'm very sorry.", "I apologize for my unprofessional behavior.", "There is no excuse for my actions. I am not worthy of this position."};
@Override
public String generateExcuse() { // Randomly select an excuse from the array
String result = "I was late today because " + excuses[(int) Math.round(Math.random() + 1)] + "\\n" +
apologies[(int) Math.round(Math.random() + 1)];
return result;
}
@Override
public void likeExcuse(String excuse) {
// Duplicate the element in the array so that its chances of being chosen are higher
}
@Override
public void dislikeExcuse(String excuse) {
// Remove the item from the array
}
}
Proviamo il nostro esempio:
Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
Produzione:
"I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
I apologize for my unprofessional behavior.
Ora immagina di aver lanciato un servizio che genera scuse, raccolto statistiche e notato che la maggior parte dei tuoi utenti sono studenti universitari. Per servire meglio questo gruppo, hai chiesto a un altro sviluppatore di creare un sistema che generi scuse specifiche per gli studenti universitari. Il team di sviluppo ha condotto ricerche di mercato, classificato scuse, collegato un po' di intelligenza artificiale e integrato il servizio con bollettini sul traffico, bollettini meteorologici e così via. Ora hai una libreria per generare scuse per gli studenti universitari, ma ha un'interfaccia diversa: StudentExcuse
.
public interface StudentExcuse {
String generateExcuse();
void dislikeExcuse(String excuse);
}
Questa interfaccia ha due metodi: generateExcuse
, che genera una scusa, e dislikeExcuse
, che impedisce che la scusa appaia di nuovo in futuro. La libreria di terze parti non può essere modificata, ovvero non è possibile modificarne il codice sorgente. Quello che abbiamo ora è un sistema con due classi che implementano l' Excuse
interfaccia e una libreria con una SuperStudentExcuse
classe che implementa l' StudentExcuse
interfaccia:
public class SuperStudentExcuse implements StudentExcuse {
@Override
public String generateExcuse() {
// Logic for the new functionality
return "An incredible excuse adapted to the current weather conditions, traffic jams, or delays in public transport schedules.";
}
@Override
public void dislikeExcuse(String excuse) {
// Adds the reason to a blacklist
}
}
Il codice non può essere modificato. L'attuale gerarchia di classi si presenta così: Questa versione del sistema funziona solo con l'interfaccia Excuse. Non è possibile riscrivere il codice: in un'applicazione di grandi dimensioni, apportare tali modifiche potrebbe diventare un processo lungo o interrompere la logica dell'applicazione. Potremmo introdurre un'interfaccia di base ed espandere la gerarchia: per fare ciò, dobbiamo rinominare l' Excuse
interfaccia. Ma la gerarchia extra è indesiderabile nelle applicazioni serie: l'introduzione di un elemento radice comune rompe l'architettura. Dovresti implementare una classe intermedia che ci consenta di utilizzare sia la nuova che la vecchia funzionalità con perdite minime. Insomma, serve un adattatore .
Il principio alla base del modello dell'adattatore
Un adattatore è un oggetto intermedio che consente alle chiamate di metodo di un oggetto di essere comprese da un altro. Implementiamo un adattatore per il nostro esempio e chiamiamoloMiddleware
. Il nostro adattatore deve implementare un'interfaccia compatibile con uno degli oggetti. Lascia che sia Excuse
. Questo permette Middleware
di chiamare i metodi del primo oggetto. Middleware
riceve le chiamate e le inoltra in modo compatibile al secondo oggetto. Ecco l' Middleware
implementazione con i metodi generateExcuse
e dislikeExcuse
:
public class Middleware implements Excuse { // 1. Middleware becomes compatible with WorkExcuse objects via the Excuse interface
private StudentExcuse superStudentExcuse;
public Middleware(StudentExcuse excuse) { // 2. Get a reference to the object being adapted
this.superStudentExcuse = excuse;
}
@Override
public String generateExcuse() {
return superStudentExcuse.generateExcuse(); // 3. The adapter implements an interface method
}
@Override
public void dislikeExcuse(String excuse) {
// The method first adds the excuse to the blacklist,
// Then passes it to the dislikeExcuse method of the superStudentExcuse object.
}
// The likeExcuse method will appear later
}
Test (nel codice client):
public class Test {
public static void main(String[] args) {
Excuse excuse = new WorkExcuse(); // We create objects of the classes
StudentExcuse newExcuse = new SuperStudentExcuse(); // that must be compatible.
System.out.println("An ordinary excuse for an employee:");
System.out.println(excuse.generateExcuse());
System.out.println("\n");
Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Wrap the new functionality in the adapter object
System.out.println("Using new functionality with the adapter:");
System.out.println(adaptedStudentExcuse.generateExcuse()); // The adapter calls the adapted method
}
}
Produzione:
An ordinary excuse for an employee:
I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
There is no excuse for my actions. I am not worthy of this position.
Using new functionality with the adapter:
Una scusa incredibile adattata alle condizioni meteorologiche attuali, agli ingorghi o ai ritardi negli orari dei trasporti pubblici. Il generateExcuse
metodo passa semplicemente la chiamata a un altro oggetto, senza ulteriori modifiche. Il dislikeExcuse
metodo ci ha richiesto di inserire prima la scusa nella lista nera. La capacità di eseguire l'elaborazione intermedia dei dati è un motivo per cui le persone adorano il modello dell'adattatore. Ma per quanto riguarda il likeExcuse
metodo, che fa parte dell'interfaccia Excuse
ma non parte dell'interfaccia StudentExcuse
? La nuova funzionalità non supporta questa operazione. Il UnsupportedOperationException
è stato inventato per questa situazione. Viene generato se l'operazione richiesta non è supportata. Usiamolo. Ecco come Middleware
appare la nuova implementazione della classe:
public class Middleware implements Excuse {
private StudentExcuse superStudentExcuse;
public Middleware(StudentExcuse excuse) {
this.superStudentExcuse = excuse;
}
@Override
public String generateExcuse() {
return superStudentExcuse.generateExcuse();
}
@Override
public void likeExcuse(String excuse) {
throw new UnsupportedOperationException("The likeExcuse method is not supported by the new functionality");
}
@Override
public void dislikeExcuse(String excuse) {
// The method accesses a database to fetch additional information,
// and then passes it to the superStudentExcuse object's dislikeExcuse method.
}
}
A prima vista, questa soluzione non sembra molto buona, ma imitare la funzionalità può complicare la situazione. Se il cliente presta attenzione e l'adattatore è ben documentato, tale soluzione è accettabile.
Quando usare un adattatore
-
Quando è necessario utilizzare una classe di terze parti, ma la sua interfaccia è incompatibile con l'applicazione principale. L'esempio precedente mostra come creare un oggetto adattatore che esegue il wrapping delle chiamate in un formato comprensibile per un oggetto di destinazione.
-
Quando diverse sottoclassi esistenti necessitano di alcune funzionalità comuni. Invece di creare sottoclassi aggiuntive (che porteranno alla duplicazione del codice), è meglio utilizzare un adattatore.
Vantaggi e svantaggi
Vantaggio: l'adattatore nasconde al client i dettagli dell'elaborazione delle richieste da un oggetto all'altro. Il codice client non pensa alla formattazione dei dati o alla gestione delle chiamate al metodo di destinazione. È troppo complicato e i programmatori sono pigri :) Svantaggio: la base di codice del progetto è complicata da classi aggiuntive. Se hai molte interfacce incompatibili, il numero di classi aggiuntive può diventare ingestibile.Non confondere un adattatore con una facciata o un decoratore
Con solo un'ispezione superficiale, un adattatore potrebbe essere confuso con i motivi della facciata e del decoratore. La differenza tra un adattatore e una facciata è che una facciata introduce una nuova interfaccia e avvolge l'intero sottosistema. E un decoratore, a differenza di un adattatore, cambia l'oggetto stesso piuttosto che l'interfaccia.Algoritmo passo dopo passo
-
Innanzitutto, assicurati di avere un problema che questo modello può risolvere.
-
Definire l'interfaccia client che verrà utilizzata per interagire indirettamente con oggetti incompatibili.
-
Fare in modo che la classe dell'adattatore erediti l'interfaccia definita nel passaggio precedente.
-
Nella classe adattatore, creare un campo per memorizzare un riferimento all'oggetto adattato. Questo riferimento viene passato al costruttore.
-
Implementare tutti i metodi dell'interfaccia client nell'adattatore. Un metodo può:
-
Trasferisci le chiamate senza apportare modifiche
-
Modificare o integrare i dati, aumentare/diminuire il numero di chiamate al metodo target, ecc.
-
In casi estremi, se un metodo particolare rimane incompatibile, genera un'eccezione UnsupportedOperationException. Le operazioni non supportate devono essere rigorosamente documentate.
-
-
Se l'applicazione utilizza solo la classe dell'adattatore tramite l'interfaccia client (come nell'esempio precedente), l'adattatore può essere espanso in modo indolore in futuro.
GO TO FULL VERSION