Adapter jest wzorcem, którego zadaniem jest konwersja jednego inteface’u w drugi, co pozwoli na wykorzystanie klasy adaptowanej tam, gdzie nie zostało to pierwotnie założone.
Wyobraźmy sobie, że kupujemy nowy monitor. Okazuje się, że monitor ma kabel który nie pasuje do naszej karty graficznej. Pierwszym skojarzeniem jest użycie odpowiedniej ‚przejściówki’ co pozwoli na podłączenie wtyczki nowego typu do gniazda starego typu. Tak działa właśnie adapter.
Przykład:
Posiadamy program pilota, który steruje całym naszym sprzętem w domu m.in. telewizorem. Oto kod pilota i interfejsu telewizora:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class UniwersalnyPilot { Telewizor tv; //.... public void wlaczToKolorowePudlo() { tv.wlaczTelewizor(); } } public interface Telewizor { public void wlaczTelewizor(); public void wylaczTelewizor(); } |
Jednak pewnego dnia otrzymujemy całkiem nową, super wypasioną 60-cio calową plazmę, wraz z klasą sterującą. Poniżej kod klasy i interface’u :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public interface Plazma { public void odpalPlazme(); public void zgasPlazme(); } public class Nowa60cioCalowaPlazma implements Plazma{ public void odpalPlazme() { //kod włączający } public void zgasPlazme() { //kod wyłączający } } |
Problem w tym, że zarówno do kodu pilota jak i sterowników plazy nie mamy bezpośrednio dostępu. Musimy zatem utworzyć ‚przejściówkę’ która będzie udawać Telewizor, a jednocześnie wykonywać kod plazmy. Są na to dwa sposoby: przez kompozycję (adapter obiektu) i przez dzedziczenie (adapter klas).
Adapter przez kompozycję:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class AdapterPrzezKompozycje implements Telewizor { private Plazma plazma; public AdapterPrzezKompozycje(Plazma plazma) { this.plazma = plazma; } public void wlaczTelewizor() { plazma.odpalPlazme(); } public void wylaczTelewizor() { plazma.zgasPlazme(); } } |
Adapter przez dziedziczenie:
1 2 3 4 5 6 7 8 9 10 11 12 |
public class AdapterPrzezDziedziczenie extends Nowa60cioCalowaPlazma implements Telewizor { public void wlaczTelewizor() { odpalPlazme(); } public void wylaczTelewizor() { zgasPlazme(); } } |
Różnica jest subtelna. W pierwszym przypadku metody delegowane są do innego obiektu, w drugim wykonywane są metody na rzecz tego samego obiektu. Należy zwrócić uwagę, że adapter obiektu ma możliwość emulowania klas pochodnych adaptowanego interface’u – adapter klas nie ma takiej możliwości. Drugim ważnym elementem jest napisywanie metod. Oba adaptery nadpisują metody interface’u, jednak adapter klas ma dostęp do niezmienionych pozostałych metod emulowanej klasy.
Oba przypadki pozwalają nam na użycie w naszym przypadku ponieważ każdy z tych adapterów implementuje interface ‚Telewizor’.
Diagram klas wzorca Adapter wygląda następująco:
Na diagramie przedstawiony jest adapter przez kompozycję.
.