Home > GOF Pattern, java > GoF Patterns: Adapter

GoF Patterns: Adapter

25 gennaio 2011

Translate in English with Google Translate
In questo articolo tratterò il pattern Adapter anche detto Wrapper

Motivazione

Si tratta di un pattern strutturale basato su classi o su oggetti in quanto è possibile ottenere entrambe le rappresentazioni. Viene utilizzato quando si intende utilizzare un componente software ma occorre adattare la sua interfaccia per motivi di integrazione con l’applicazione esistente.

Questo comporta la definizione di una nuova interfaccia che deve essere compatibile con quella esistente in modo tale da consentire la comunicazione con l’interfaccia da “adattare”. In tale contesto possono essere anche effettuate delle trasformazioni di dati per cui l’Adapter si occuperà di interfacciarsi con il nuovo sistema e fornisce anche le regole di mapping dei dati.
Come abbiamo accennato, tale pattern può essere basato sia su classi che su oggetti pertanto l’instanza della classe da adattare può derivare da ereditarietà oppure da associazione.

Partecipanti e Struttura

Questo pattern è composto dai seguenti partecipanti:

  1. Client: colui che effettua l’invocazione all’operazione di interesse
  2. Target: definisce l’interfaccia specifica del dominio applicativo utilizzata dal Client
  3. Adaptee: definisce l’interfaccia di un diverso dominio applicativo da dover adattare per l’invocazione da parte del Client
  4. Adapter: definisce l’interfaccia compatibile con il Target che maschera l’invocazione dell’Adaptee

Abbiamo visto precedentemente che il pattern può essere basato su Classi o su Oggetti, in base a questo possiamo schematizzare in UML la relazione esistente tra l’adattatore e l’adattato ( Adapter e Adaptee )

    1. sotto forma di ereditarietà come nel caso seguente:

Adapter Pattern based on Class

    1. sotto forma di associazione come nel caso seguente:

Adapter Pattern based on Object

Conseguenze

Tale pattern presenta i seguenti vantaggi/svantaggi:

  1. Class Adapter: prevede un rapporto di ereditarietà tra Adapter e Adaptee, in cui Adapter specializza Adaptee, pertanto non è possibile creare un Adapter che specializzi più Adaptee. Se esiste una gerarchia di Adaptee occorre creare una gereachia di Adapter.
  2. Object Adapter : prevede un rapporto di associazione tra Adapter e Adaptee, in cui Adapter instanzia Adaptee, pertanto è possible avere un Adapter associato con più Adaptee.

Implementazione

Come esempio pensiamo al caso in cui dobbiamo gestire l’elenco dei dipendenti di una società. I loro dati vengono memorizzati in un java bean dal nome Impiegati che contiene tutte le informazioni personali  (nel nostro caso per semplicità indichiamo solo il cognome ).

Per effetto di una fusione societaria con una società straniera, il numero dei dipendenti aumenta ed occorre integrare il loro java bean dal nome Employer con quello esistente dal nome Impiegati. Semanticamente è uguale ma sintatticamente è diverso pertanto creiamo la classe AdattatoreEmployer per adattare la classe Employer.

Sappiamo che possiamo utilizzare sia l’Object Adapter che il Class Adapter, la differenza principale dipende dal fatto che nel secondo caso è richiesta l’ereditarietà multipla ed in java non è possibile, o meglio, non è possibile ereditare più classi ma è possibile implementare più interfacce. Questo significa che qualora disponiamo di una interfaccia Target ed un’interfaccia Adaptee possiamo utilizzare anche il Class Adapter.

Object Adapter

Per cominciare iniziamo ad implementare Object Adapter per eseguire l’esempio precedente.

Vediamo come si presenta il pattern in UML in base all’esempio nel caso di Object Adapter:

Adapter Object

Adapter Object

A questo punto passiamo a creare la classe Impiegato e la classe Employer

package patterns.adapter.object;

public class Impiegato {

    private String cognome = null;

    public String getCognome() {
        return cognome;
    }

    public void setCognome(String cognome) {
        this.cognome = cognome;
    }

}
package patterns.adapter.object;

public class Employer {

    private String lastName = null;

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

Creiamo la classe Adapter AdattatoreEmployer che eredita la classe Impiegato ed è associata con la classe Employer.

package patterns.adapter.object;

public class AdattatoreEmployer extends Impiegato {

    Employer employer = null;

    public AdattatoreEmployer(Employer employer) {
        this.employer = employer;
    }

    @Override
    public String getCognome() {
        return employer.getLastName();
    }

    @Override
    public void setCognome(String cognome) {
        employer.setLastName(cognome);
    }
}

L’invocazione avviene ad opera della classe Client che, come vediamo di seguito, passa per l’Adapter per invocare la classe Employer utilizzando gli stessi metodi utilizzati per invocare la classe Impiegato.

package patterns.adapter.object;

public class Client {

    public static void main(String[] args) {
        Impiegato impiegato = new Impiegato();
        impiegato.setCognome("Rossi");
        System.out.println("Impiegato: " + impiegato.getCognome());

        AdattatoreEmployer adattatoreEmployer = new AdattatoreEmployer( new Employer() );
        adattatoreEmployer.setCognome("Verdi");
        System.out.println("AdattatoreEmployer: " + adattatoreEmployer.getCognome());
    }

}

L’output del Client è mostrato di seguito:

$JAVA_HOME/bin/java patterns.adapter.object.Client
Impiegato: Rossi
AdattatoreEmployer: Verdi

Class Adapter

Sicuramente più complesso è il caso del Class Adapter.
Vediamo di seguito il Class Diagram che ci mostra che l’Adapter eredita sia da Target che da Adaptee. Nel caso precedente Target ed Adaptee erano due classi pertanto non era possibile l’ereditarietà multipla in java quindi ho previsto due interfacce:

  1. una del Target dal nome InterfacciaImpiegato
  2. una di Adaptee dal nome InterfacciaEmployer
Adapter Class

Adapter Class

Le due interfacce definiscono i metodi presenti nelle due organizzazioni:

package patterns.adapter.classes;

public interface InterfaceImpiegato {

    public String getCognome();

    public void setCognome(String cognome);
}
package patterns.adapter.classes;

public interface InterfaceEmployer {

    public String getLastName();
 
    public void setLastName(String lastName);
}

Le classi Impiegato ed Employer ereditano dalle due interfacce nel modo seguente:

package patterns.adapter.classes;
 
public class Impiegato implements InterfaceImpiegato {
 
    private String cognome = null;
 
    @Override
    public String getCognome() {
        return cognome;
    }
 
    @Override
    public void setCognome(String cognome) {
        this.cognome = cognome;
    }
 
}
package patterns.adapter.classes;
 
public class Employer implements InterfaceEmployer {
 
    private String lastName = null;
 
    @Override
    public String getLastName() {
        return lastName;
    }
 
    @Override
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

L’Adapter dal nome AdattatoreEmployer può ereditare dalle due interfacce ed espone i metodi previsti da Target ed Adaptee.

package patterns.adapter.classes;
 
public class AdattatoreEmployer extends Employer implements InterfaceEmployer, InterfaceImpiegato {
 
    @Override
    public String getCognome() {
        return getLastName();
    }
 
    @Override
    public void setCognome(String cognome) {
        setLastName(cognome);
    }
}

L’invocazione da parte del Client consente di invocare Employer passando per la classe Adapter AdattatoreEmployer, come nel caso precedente Object Adapter.

package patterns.adapter.classes;

public class Client {

    public static void main(String[] args) {
        Impiegato impiegato = new Impiegato();
        impiegato.setCognome("Rossi");
        System.out.println("Impiegato: " + impiegato.getCognome());

        AdattatoreEmployer adattatoreEmployer = new AdattatoreEmployer();
        adattatoreEmployer.setCognome("Verdi");
        System.out.println("AdattatoreEmployer: " + adattatoreEmployer.getCognome());
    }
}

L’output della classe Client è il seguente:

$JAVA_HOME/bin/java patterns.adapter.classes.Client
Impiegato: Rossi
AdattatoreEmployer: Verdi
Categorie:GOF Pattern, java
  1. Francesco
    29 luglio 2013 alle 11:33 AM

    Per il Class Adapter, AdattatoreEmployer possiede due metodi che effettuano un’inutile ulteriore chiamata: getLastName() chiama getCognome() …. che chiama getLastName() dell’Adaptee. E’ sufficiente chiamare il metodo dell’Adaptee ovvero -> “return employer.getLastName();” in getLastName(), risparmiando così una chiamata a metodo.
    Ciao e complimenti per questi tutorial🙂

    • 29 luglio 2013 alle 2:51 PM

      ok, ma in effetti se AdattatoreEmployer estende Employer non ho neanche bisogno di ridefinire il loro comportamento.
      Grazie anche a te x il tuo contributo.🙂

  2. andriun
    10 ottobre 2014 alle 3:08 PM

    Buonasera, mi rendo conto che si tratta di una banalità, ma solo per rendere più chiaro l’esempio forse sarebbe meglio togliere, per il caso Class Adapter, l’implementazione alla classe Adapter, dal momento che dovrebbe già essere presente con l’extends.

    • 28 ottobre 2014 alle 3:05 PM

      Si certo, praticamente si può anche togliere l’implementazione di InterfaceEmployer considerando che estende Employer ma ho preferito lasciarla per mostrare che l’Adapter implementi le interfacce dell’Adaptee.
      La differenza principale tra Object e Class Adapter sta nel modo in cui viene recuperata la referenza della classe Adaptee: nel primo caso tramite invocazione “new” mentre nel secondo caso tramite eredita’ “extends/super” (Composition over inheritance), successivamente la gestione è la stessa.
      I problemi, nel caso del Class Adapter, nascono quando occorre adattare più Adaptee, come facciamo a recuperare la referenza tramite eredità singola in java?
      In C++ è semplice in quanto ereditiamo da più classi mentre in java possiamo solo implementare le interfacce.
      Immaginiamo di dover adattare anche la classe Employe:

      package patterns.adapter.classes;
      
      public class Employe implements InterfaceEmploye {
      
          private String nomDeFamille = null;
      
          @Override
          public String getNomDeFamille() {
              return nomDeFamille;
          }
      
          @Override
          public void setNomDeFamille(String nomDeFamille) {
              this.nomDeFamille = nomDeFamille;
          }
      }
      

      l’Adapter AdattatoreEmployer dovrà implementare l’interfaccia InterfaceEmploye:

      package patterns.adapter.classes;
      
      public interface InterfaceEmploye {
      
          public String getNomDeFamille();
      
          public void setNomDeFamille(String nomDeFamille);
          
      }
      

      ma non potrà estendere la classe Employe perchè già estende Employer, cioè non possiamo scrivere:

      public class AdattatoreEmployer extends Employer, Employe implements InterfaceEmployer, InterfaceImpiegato, InterfaceEmploye { ... }
      

      ma possiamo creare un nuovo Adapter AdattatoreEmploye:

      package patterns.adapter.classes;
      
      public class AdattatoreEmploye extends Employe implements InterfaceEmploye, InterfaceImpiegato {
      
          @Override
          public String getCognome() {
              return getNomDeFamille();
          }
      
          @Override
          public void setCognome(String cognome) {
              setNomDeFamille(cognome);
          }
          
      }
      

      In questo caso continuiamo ad utilizzare l’ereditarietà per ottenere la reference all’oggetto Adaptee ma dobbiamo creare un nuovo adapter.
      Il problema è che il Class Adapter si sposa bene con linguaggi ad oggetti che prevedono l’ereditarietà multipla (C++) ma laddove questa non sia possile (java) il suo utilizzo diventa forzato soprattutto nel caso in cui esiste una classe concreta Adaptee e la si vuole utilizzare estendendola e non istanziandola.
      Infatti la prassi in java è quella di referenziare l’oggetto Adaptee istanziandola e non ereditandola anche nel caso del Class Adapter, ovviamente funziona ugualmente ma perde, secondo me, il suo spirito iniziale.
      Per esempio implementazioni del Class Adapter che utilizzano l’aggregazione e non l’ereditarietà per ottenere la reference: http://userpages.umbc.edu/~tarr/dp/lectures/Adapter-6pp.pdf
      Applicando questo approccio, la referenza all’oggetto verrebbe effettuata con l’aggregazione e non con l’ereditarietà, per esempio in questo modo:

      package patterns.adapter.classes;
      
      public class AdattatoreEmploye implements InterfaceEmploye, InterfaceImpiegato {
          private Employe employe;
      
          public AdattatoreEmploye(Employe employe){
              this.employe = employe;
          }    
          
          @Override
          public String getCognome() {
              return employe.getNomDeFamille();
          }
      
          @Override
          public void setCognome(String cognome) {
              employe.setNomDeFamille(cognome);
          }
      
          @Override
          public String getNomDeFamille() {
              throw new UnsupportedOperationException("Not supported yet.");
          }
      
          @Override
          public void setNomDeFamille(String nomDeFamille) {
              throw new UnsupportedOperationException("Not supported yet.");
          }
      
      }
      
  1. No trackbacks yet.
I commenti sono chiusi.
%d blogger cliccano Mi Piace per questo: