© 1999-2003, Flemming Koch Jensen
Alle rettigheder forbeholdt
Adapter Pattern

Opgaver

"You will adapt."

- Seven of nine - Star Trek Voyager

 

 

Motivation
Et af slagordene i objektorientering er "genbrug". Det, man stiler mod at kunne genbruge, er klasser. Et af de problemer man kan støde på, hvis man vil genbruge en klasse fra et tidligere projekt, er at den ikke helt passer ind i det design, man vil bruge i det nye system. Problemet kan være, at interfacet ikke passer med det, som vores klient ønsker/forventer.
Et klassisk eksempel1 er klasser til repræsentation af grafiske elementer i en grafisk applikation. Betragt følgende klassediagram:
Figur 1:
Design med grafiske elementer
Vores grafiske applikation skal naturligvis bruge betydelig flere grafiske elementer end disse to, men vi vil holde eksemplet simpelt, og lade Linie repræsentere alle grafiske elementer, pånær tekst.
Vi har valgt dette design, og er i forbindelse med klassen Tekst stødt på en tidligere klasse TextBox, som godt kan løse opgaven, men har et andet interface end det ønskede. Vi kunne nu vælge at ændre interfacet for de øvrige klasser, så vi kan indplacere TextBox som subklasse til GrafiskElement. Der er blot et problem! TextBox blev lavet til en anden applikation og har ikke nogen move- eller resize-metode. Ved at kalde en række andre metoder kan man dog få realiseret disse funktionaliteter, men det er nu klientens opgave at gøre dette for alle grafiske elementer som den ønsker at flytte eller ændre størrelse på. Vi har løst designproblemet ved at vælte det over på klienten. Denne form for løsning er i modstrid med et af vores overordnede designmål, nemlig at gøre tilværelsen så let som mulig for klienten. Der er derfor behov for en anden løsning.

 

Problem

En klient ønsker at anvende et andet interface end det objektet har.

 

Løsning

Delegering Løsningen ligger i delegering. Vi lader vores "egne" klasser beholde det interface som passer til vores nye anvendelse og placerer interface-problemet i et objekt mellem klienten og instansen af TextBox. Vi lader dette objekt have det samme interface som de andre grafiske elementer, så klienten uden nogen tilpasning kan anvende det.
Vores klassediagram bliver:
Figur 2:
Object-adapter
Lad os for eksemplets skyld antage at resize i interfacet GrafiskElement er erklæret som:
void resize( int xdim, int ydim )
hvor xdim og ydim skal være det grafiske elements nye størrelse i de to dimensioner.
I TextBox findes der som bekendt ikke en sådan metode, men den har i stedet følgende to metoder, der til sammen kan løse denne opgave:
void resizeX( int xsize )
void resizeY( int ysize )
Ved at oversætte en modtaget resize-request fra klienten til to requests til TextBox'en kan Text-objektet løse opgaven, som det er illustreret i følgende samarbejdsdiagram:
Figur 3:
Oversættelse af request til to requests
Tolk At Text-objektet fungerer som en tolk mellem de to objekter har lagt navn til Adapter Pattern, idet tolken kaldes en adapter.

 

Klassediagram

Det generelle klassediagram har følgende betegnelser:
Figur 4:
Betegnelser i Adapter Pattern
Target er det klienten gerne vil have, mens Adaptee er det vi har. Løsningen er at lave Adapter.
Adapter Pattern adskiller sig fra andre design patterns, ved at den kan designes på to forskellige måder. Ovenfor har vi konstrueret løsningen ved komposition (sammensætning) af objekter, men den kan også laves ved nedarvning.
Betragt følgende klassehierarki:
Figur 5:
Class- Adapter
Her vil Adapter's interface bestå af det kombinerede Target- og Adaptee-interface. Klienten kender kun Target-interfacet og det er derfor ikke noget problem at Adapter's interface indeholder alle metoderne fra Adaptee - klienten kan hverken se eller anvende dem!
En Adapter der er lavet vha. komposition kaldes en object-adapter, mens en Adapter der er lavet vha. nedarvning kaldes en class-adapter. Betegnelser der normalt kun bruges når man taler om dem begge.
Ovenfor har vi valgt at gøre Target til et interface. Det er betingelsen for at anvende en class-adapter i Java, der som bekendt kun tillader begrænset multipel nedarvning. I andre sprog (eg. C++), som tillader "rigtig" multipel nedarvning, har man mulighed for at lade Target være en klasse som alle andre, og dermed kunne tilbyde implementation til subklasser.

 

Interaktion

Som følgende sekvensdiagram illustrerer, består interaktionen mellem Adapter og Adaptee i et eller flere requests for hver request Adapter modtager fra klienten.
Figur 6:
En request fra klienten bliver til en eller flere requests til Adaptee
Vi har her valgt at inkludere returneringen fra Adapter, for at understrege Adapter's simulering, overfor klienten, af at det er den der løser opgaven (se evt. Proxy Pattern).
Meget eller lidt adapter Interaktionen mellem Adapter og Adaptee kan have forskelligt omfang. Man bruger betegnelserne meget eller lidt adapter til at katagorisere adaptere. Hvis interaktionen er meget kompleks betegnes Adapter som meget adapter, mens den kaldes lidt adapter; hvis den er meget simpel. Betegnelserne er relative og anvendes normalt kun til at sammenligne forskellige adaptere.

 

Implementation

Implementationen er i høj grad case-afhængig. Hvis oversættelsen er kompliceret kan de enkelte metoder være komplicerede at lave, og man kan ikke give nogen generelle anvisninger.

 

Varianter

 

Handle Pattern

Man kan se Handle Pattern som en variant af Adapter Pattern. I Handle Pattern er Adapteren (der hedder et Handle) en lidt adapter. Der er dog den væsentlige forskel på de to design patterns, at Adapter Pattern retter sig mod anvendelse af en klasse, der allerede eksisterer, mens Handle Pattern blot ønsker at adskille interface og implementation. I Handle Pattern har Adapter (Handle) derfor det samme interface som Subject (Body) (Betegnelserne fra Handle Pattern er anført i parantes).

 

Two-Way Adapter

Hvis man arbejder med et legacy system, kan man have behov for at lave Two-Way Adaptere.
  En Two-Way Adapter er en Adapter, der kan optræde i begge roller: Både som den gamle Adaptee og som den nye Target. Man laver nemmest en Two-Way Adapter som en class-adapter. På den måde får man et objekt, der kan anvendes af klienter, som "ser" det samme objekt gennem forskellige interfaces.
  I følgende figur er det illustreret hvordan to klienter, en fra det gamle system og en fra det nye, bruger det samme objekt.
Figur 7:
Two-Way Adapter
Two-Way Adapter'e er relativ sjældne, men teoretisk værd at bemærke.

 

Relationer til andre patterns

  Et Handle i Handle Pattern er en lidt adapter, da interaktionen ikke kan blive mere simpel.
  At Adapter holder Adaptee's eksistens skjult for klienten gør den til en Proxy i Proxy Pattern.
 
fodnoter:
Det er det introducerende eksempel i [GoF94] på side 139-40.

 

Referencer

  [GoF94] s.139-150.
  [Grand98] s.177-183.
  Adapter Pattern introduceres i kapitlet Komposition i sammenhæng med Handle Pattern.