MokaByte Numero 33  -  Settembre  99
Il pattern adapter
la pratica
di 
Andrea Trentini
L'adapter nella vita quotidiana,  nel JDK e un esempio realizzato appositamente


 

Finiamo a "birra e salsicce" come in un divertente film dell'indimenticabile coppia 
Hill-Spencer ;-)  Al solito il sorgente lo trovate qui.

Nella vita quotidiana...

...la parola "adattatore" viene usata spesso, vi è mai capitato di viaggiare all'estero?
Se andate in Inghilterra o in Francia e volete portarvi degli elettrodomestici (naturalmente includo anche il PC) dovete preoccuparvi di portare con voi degli ADATTATORI, non fosse altro che per la mania tipica di ogni paese nell'inventare tipi di prese/spine rigorosamente diversi da ogni altro presente sulla faccia della terra (e non ;-).
Per cui vedete i cosiddetti "frequent flyers" (chi viaggia spesso) armati di fondine con tutti i tipi di spine possibili e immaginabili (ne esistono di divertentissimi, sembrano dei cubi di Rubik quando si cerca di smontarli o rimontarli).
Tali adattatori sono abbastanza stupidi, non perchè sia difficile smontarli o rimontarli (in questo caso infatti non è lui ad essere stupido...), ma perchè NON si preoccupano di operare degli adattamenti di tensione/frequenza/etc. Parlando in termini software si potrebbe dire che questi adattatori si limitano a tradurre un interfaccia, senza aggiungere funzionalità (se un americano viene da noi e si limita a cambiare spina il suo povero apparecchio farà una brutta fine... verrà generata una "Fulminated&BurntException: sorry, too many volts, buy another appliance" ;-).
Esistono infatti degli adattatori più "intelligenti", ad esempio quelli che operano anche una "traduzione" di tensione (e/o frequenza) che vi permettono infatti di usare le vostre apparecchiature negli USA (dove la tensione è a 110V e a 60Hz). Ovviamente sono più complessi e costosi, nonchè più pesanti (nel caso software il peso equivale alla dimensione dell'eseguibile... ;-), ma aggiungono funzionalità, vi permettono cose che un adattatore di spina/presa non vi avrebbe consentito.
Anche nel software le cose non cambiano, l'Adapter più semplice è quello che opera soltanto una traduzione di nomi dei metodi (e delega ogni comportamento all'Adaptee), mentre Adapter più complessi prima di passare il controllo al delegato (Adaptee) magari effettuano alcuni controlli di coerenza locale o delle conversioni (sarà il nostro esempio).
Nel JDK...
... si possono incontrare frequentemente gli Adapter, vi descrivo due esempi, a voi il "gioco" di trovarne degli altri.
Wrappers
Immagino vi sia capitato di dover convertire numeri fra basi diverse o di dover leggere una stringa (che rappresentava un numero) e tradurla in un numero vero (inteso come tipo primitivo di Java). Anche se in vita mia ho visto tanta gente reinventare la ruota o riscoprire l'acqua calda (l'ho fatto anch'io, l'importante è cercare di accorgersi ;-), spero abbiate usato le cosiddette "wrapper classes" (in italiano si potrebbe dire "incapsulatori"). Un Integer non è altro che l'Adapter di un int (Adaptee), anche se l'adattato non è una classe non vuol dire che il pattern cada...
Integer, Byte, Float, etc. sono tutte wrapper classes dei rispettivi tipi primitivi (int, byte, float, etc.), volendo vi permettono l'uso di un dato di tipo primitivo come se fosse un oggetto (ovviamente con un certo grado di appesantimento, ma questo è il difetto principale del pattern Adapter: aggiunge un gradino di indirezione).
AutoAdapter
Esistono anche classi che hanno più facce, sono cioè adapter di se stesse...
Vi siete domandati come mai in alcune classi ci sono metodi con nomi diversi ma che sembra siano gemelli (funzionalmente)? Non ne avete mai trovate? Provate ad aprire la documentazione del Vector (in java.util), troverete alcuni metodi che (sebbene abbiano nomi diversi) apparentemente fanno la stessa cosa:
  • get(int)
    public synchronized Object get(int index) {
    
       if (index >= elementCount)
    
          throw new ArrayIndexOutOfBoundsException(index);
    
          return elementData[index];
    
    }
  • elementAt(int)

  •  

     
     
     
     
     
     

     

    public synchronized Object elementAt(int index) {
       if (index >= elementCount) {
          throw new ArrayIndexOutOfBoundsException
                   (index + " >= " + elementCount);
       }
       try {
          return elementData[index];
       catch (ArrayIndexOutOfBoundsException e) {
          throw new ArrayIndexOutOfBoundsException
             (index + " < 0");
       }
     }

Non vi sembrano pressochè identici? In effetti lo sono (a parte un try in più). Questo perchè il Vector fa da adattatore di se stesso (un po' come certi alimentatori dei pc, che si adattano a varie tensioni senza dover toccare nessuno switch). Il Vector è stato fatto così perchè deve implementare anche l'interfaccia List, e infatti get(int) viene da proprio da List mentre elementAt(int) è "proprio" del Vector stesso, la documentazione di elementAt(int) recita: "Returns the component at the specified index. This method is identical in functionality to the get method (which is part of the List interface)". Se mi chiedete perchè hanno duplicato anche il corpo del metodo invece di delegare uno all'altro (una sorta di "delega interna") vi rispondo con un'ipotesi: credo abbiano voluto privilegiare la velocità di esecuzione rispetto alla dimensione della classe.
Situazione analoga nel caso di:
  • remove(Object)
  • removeElement(Object)
Inoltre ogni container fornisce (nel senso di "genera") anche un Adapter di se stesso: le Enumeration...
 
 
 
 

Una birreria all'italiana...

E veniamo al nostro esempio, fatto apposta per creare una relazione adattatore/adattato.
Supponiamo di dover impiantare una birreria italiana in Italia... e supponiamo anche che per qualche assurda legge (temo che non sia un'ipotesi tanto strana) si debba vendere la birra usando il nostro sistema di misura (in litri) invece di quello inglese (pinte e galloni).
Se però vogliamo importare birra dall'estero, ad esempio dall'Inghilterra, questa ci arriverà in fusti, misurati in pinte (o galloni)... come faremo ad adattarci alle leggi italiane? Ma installando un adattatore, che diamine!
Le classi in gioco sono solo tre (più Birreria che fa da main): Keg (il barile inglese, l'adattato), Fusto (l'adattatore italiano) e Glass (di cui non ho creato una versione italianizzata ;-).
Il Fusto si può creare soltanto a partire da un Keg esistente e l'invocazione dei metodi di Fusto scatena (previe eventuali conversioni, guardate il sorgente) invocazioni di metodi del Keg.
L'essenziale del Fusto è che viene spillato in litri (frazioni di) invece che in pinte (frazioni di) come il Keg e che tutte le informazioni sul proprio stato le fornisce in unità italiane (effettuando le conversioni delle corrispondenti informazioni prese dal Keg).
 
 
 
 

Il consueto schema UML...

Su Glass non ho messo relazioni poichè non è così fondamentale, non è uno dei partecipanti.
 
 

 

Output

Se compilate i sorgenti e lanciate "java Birreria" otterrete questo output:
-----------------
Keg@f0d05063, level (%)=100.0, level (pints)=80.0
Fusto@f1585063, livello (%)=100.0, livello (litri)=37.855484
-----------------
bicchiere da un litro
Glass@fc385063, level (12th of pint)=25.3596
bicchiere da un litro e mezzo
Glass@fccc5063, level (12th of pint)=38.039402
-----------------
Keg@f0d05063, level (%)=93.39593, level (pints)=74.71674
Fusto@f1585063, livello (%)=93.39593, livello (litri)=35.355484
-----------------

Come vedete vengono spillati due bicchieri di birra attraverso il Fusto (italiano), per un totale di due litri e mezzo. Anche il Keg ovviamente scende di livello (poichè il Fusto spilla a sua volta dal Keg). Come noterete la percentuale rimane identica (infatti la percentuale è un numero puro, indipendente dalle unità di misura), è un modo per controllare di aver scritto correttamente il codice ;-).
 
 
 

 

Apollo13 

Avrete visto il film immagino... Vi ricordate il problema della CO2? Erano in tre nel LEM (progettato per due persone e per meno giorni) per cui il livello di CO2 stava salendo paurosamente. Pensarono quindi di usare i filtri della capsula (inutilizzati) nel LEM... peccato che erano quadrati invece che rotondi (o vice versa, non ricordo) per cui NON potevano entrare nell'alloggiamento apposito!!! Cosa gli è toccato fare? Hanno dovuto costruirsi "al volo" (e "in volo" da giorni, stanchi e già mezzi intontiti dalla CO2) un adattatore (eh, eh ;-) con i pochi mezzi a disposizione nella capsula. Quando si
dice la "progettazione orientata al riuso"... I progettisti non avevano pensato ad un possibile riutilizzo (in caso di emergenza ad esempio) dei filtri CO2 tra capsula e LEM. Da allora la NASA ha imparato molto e ora (almeno così dicono) cercano sempre di standardizzare i vari connettori e alloggiamenti (cioè le interfacce, per parlare in termini SW ;-). 
 
 

MokaByte rivista web su Java

MokaByte ricerca nuovi collaboratori
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it