Wzorzec projektowy Adapter

Adapter można porównać do przejściówki miedzy między różnego rodzaju kablami np. przejściówka miedzy kablem usb a rs232 do podłączenia myszki.

Adapter zmienia interface klasy B w interface klasy A który rozumie klasa C.

Alternatywna nazwa wzorca - Wrapper, która oznacza opakowanie, bardzo dobrze opisuje rolę obiektu Adapter: pełnić wobec Klienta rolę otoczki, która umożliwia przetłumaczenie jego żądań na protokół zrozumiały dla faktycznego wykonawcy poleceń.

Zastosowanie wzorca adapter pozwala dopasować istniejące obiekty do tworzonych struktur klas i uniknąć ograniczeń związanych z ich interfejsem oraz jakiejkolwiek zmiany kodu w klasach.

Zastosowanie:

  • Gdy klasa nie może być użyta ponieważ ma niekompatybilny interfejs
  • gdy nie mamy kodu źródłowego klas nie możemy zmienić ich interfejsu a nawet jeśli mamy kod źródłowy klasy nie powinniśmy raczej zmieniać interfejsu klasy!

Struktura

Struktura wzorca składa się z trzech podstawowych klas: Target, Adaptee oraz Adapter. Target jest interfejsem, którego oczekuje klient. Obiektem dostarczającym żądanej przez klienta funkcjonalności, ale niezgodnego pod względem typu, jest Adaptee. Rolą Adaptera, który implementuje typ Target, jest przetłumaczenie wywołania metody należącej do typu Target poprzez wykonanie innej metody (lub grupy metod) w klasie Adaptee. Dzięki temu klient współpracuje z obiektem Adapter o akceptowanym przez siebie interfejsie Target, jednocześnie wykorzystując funkcjonalność dostarczoną przez Adaptee.
Wzorzec ten posiada także wersję wykorzystującą dziedziczenie w relacji Adapter-Adaptee. Jednak wersja ta ma pewne niedogodności: powiązania między obiektami są ustalane w momencie kompilacji i nie mogą ulec zmianie; ponadto, język programowania musi umożliwiać stosowanie wielokrotnego dziedziczenia lub dziedziczenia i implementacji interfejsu (jak w przypadku języków Java i C#).

Koncepcja

Koncepcja wzorca ADAPTER'a jest bardzo prosta: piszemy klasę, która posiada wymagany interfejs, a następnie zapewniamy jej komunikację z klasą, która ma inny interfejs. Istnieją dwa sposoby realizacji:

  • poprzez dziedziczenie
  • poprzez kompozycję
  1. W pierwszym wypadku z klasy, która ma niezgodny interfejs wywodzimy klasę pochodną i dopisujemy nowe metody tak, by uzyskać wymagany interfejs.
  2. W drugim przypadku zawieramy pierwotną klasę wewnątrz nowej i tworzymy metody wymaganego interfejsu realizujące wywołania metod klasy wewnętrznej.

ADAPTER - implementacja poprzez kompozycje

public class Adoptowana {
   public Adoptowana() { };
   public int stareZadanie() {return 1;};
}

public class AdapterComposition implements WymaganyIF {
   private Adoptowana adoptowana;
   public Adapter() {
      adoptowana = new Adoptowana();
   }
   public int noweZadanie {
      return adoptowana.stareZadanie();
   }
}

public interface WymaganyIF {
   public int noweZadanie();
}

ADAPTER - poprzez dziedziczenie

public class Adoptowana {
	public Adoptowana() { }
	public int stareZadanie() {return 1;}
}
public class AdapterInherit extends Adoptowana implements WymaganyIF {
	public Adapter() {}
	public int noweZadanie {
		return stareZadanie();
}
}
public interface WymaganyIF {
	public int noweZadanie();
}