CodeGym /Java Blog /Random-IT /Quali problemi risolve il modello di progettazione dell'a...
John Squirrels
Livello 41
San Francisco

Quali problemi risolve il modello di progettazione dell'adattatore?

Pubblicato nel gruppo Random-IT
Lo sviluppo del software è reso più difficile da componenti incompatibili che devono lavorare insieme. Ad esempio, se devi integrare una nuova libreria con una vecchia piattaforma scritta in versioni precedenti di Java, potresti incontrare oggetti incompatibili, o meglio interfacce incompatibili. Quali problemi risolve il modello di progettazione dell'adattatore?  - 1Cosa fare in questo caso? Riscrivere il codice? Non possiamo farlo, perché l'analisi del sistema richiederà molto tempo o la logica interna dell'applicazione verrà violata. Per risolvere questo problema, è stato creato il modello dell'adattatore. Aiuta gli oggetti con interfacce incompatibili a lavorare insieme. Vediamo come usarlo!

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'interfaccia Excuseche ha generateExcuse()e metodi. likeExcuse()dislikeExcuse()

public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
La WorkExcuseclasse 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' Excuseinterfaccia e una libreria con una SuperStudentExcuseclasse che implementa l' StudentExcuseinterfaccia:

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ì: Quali problemi risolve il modello di progettazione dell'adattatore?  - 2Questa 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: Quali problemi risolve il modello di progettazione dell'adattatore?  - 3per fare ciò, dobbiamo rinominare l' Excuseinterfaccia. 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 chiamiamolo Middleware. Il nostro adattatore deve implementare un'interfaccia compatibile con uno degli oggetti. Lascia che sia Excuse. Questo permette Middlewaredi chiamare i metodi del primo oggetto. Middlewarericeve le chiamate e le inoltra in modo compatibile al secondo oggetto. Ecco l' Middlewareimplementazione con i metodi generateExcusee 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 generateExcusemetodo passa semplicemente la chiamata a un altro oggetto, senza ulteriori modifiche. Il dislikeExcusemetodo 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 likeExcusemetodo, che fa parte dell'interfaccia Excusema 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 Middlewareappare 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

  1. 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.

  2. 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

  1. Innanzitutto, assicurati di avere un problema che questo modello può risolvere.

  2. Definire l'interfaccia client che verrà utilizzata per interagire indirettamente con oggetti incompatibili.

  3. Fare in modo che la classe dell'adattatore erediti l'interfaccia definita nel passaggio precedente.

  4. Nella classe adattatore, creare un campo per memorizzare un riferimento all'oggetto adattato. Questo riferimento viene passato al costruttore.

  5. 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.

  6. 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.

Certo, questo design pattern non è una panacea per tutti i mali, ma può aiutarti a risolvere elegantemente il problema dell'incompatibilità tra oggetti con interfacce diverse. Uno sviluppatore che conosce i modelli di base è parecchi passi avanti rispetto a chi sa solo come scrivere algoritmi, perché i modelli di progettazione sono necessari per creare applicazioni serie. Il riutilizzo del codice non è così difficile e la manutenzione diventa piacevole. È tutto per oggi! Ma presto continueremo a conoscere vari design pattern :)
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION