Adaptér (návrhový vzor)
Adaptér je návrhový vzor.
Účel
Již název tohoto návrhového vzoru napovídá, že hlavní problematikou, na kterou budeme návrhový vzor adaptér aplikovat, je změna rozhraní třídy na rozhraní jiné, nebo další v pořadí, které potřebujeme, aby naše třída implementovala. Adaptér umožní spolupráci třídám, které by vzhledem k rozdílným rozhraním jinak nespolupracovaly. Způsob, jakým tohoto účelu dosáhnout, spočívá ve vložení třídy adaptéru mezi tyto 2 třídy. Existuje hned několik způsobů jak adaptér implementovat. Podoba názvu tohoto návrhového vzoru s reálným adaptérem, tedy například adaptérem laptopu, není náhodná, jelikož reálný adaptér také přizpůsobuje napětí sítě na napětí požadované laptopem.
V jazyce Java se lze s příkladem adaptéru jako návrhového vzoru nejčastěji setkat při práci s tzv. obalovými třídami, ty jsou ve světě vývojářů často označovány anglickým slovem wrapper. Tyto wrappery přizpůsobují rozhraní primitivních datových typů tak, aby s nimi bylo zacházeno jako s objektovými typy tím, že primitivní datový typ obalí adaptérem, jenž implementuje požadované vlastnosti objektového typu (nebo se alespoň tváří, že je implementuje). Tento adaptér tedy přidá primitivním datovým typům funkcionalitu, díky níž se vůči okolnímu světu klasifikují na objektové typy.
Způsoby implementace
Návrhový vzor adaptér lze implementovat třemi způsoby. Každý z nich má odlišné vlastnosti a každý se hodí pro trochu jiný účel. Zde jsou popisy všech tří způsobů s jejich class diagramy na obrázku a jednoduchou kostrou implementace v jazyce Java. Mnohé poznatky o problematice návrhového vzoru adaptér, které pomohly ke vzniku tohoto článku, jakožto i předlohy pro class diagramy byly čerpány z publikace Rudolfa Pecinovského (2007)
Univerzální adaptér
Univerzální adaptér použijeme v situaci, kdy klient požaduje po třídách, aby implementovaly určité rozhraní. Adaptér je definován jako společný předek pro všechny tyto třídy a v něm jsou implementovány implicitní verze všech metod požadovaných rozhraním. Metody mohou být definovány jako prázdné, nebo mohou obsahovat příkaz na vyhození výjimky UnsupportedOperationException. Adaptér tohoto typu může být implementován jako třída i jako rozhraní. Nevýhodou Univerzálního adaptéru je fakt, že se dá použít pouze v případě, kdy definujeme novou třídu (Adaptér), která nemusí být součástí žádné dědické hierarchie. Při tomto řešení je také potřeba zasahovat do adaptovaných tříd.
Kostra implementace:
//Klientem požadované rozhraní a v něm uvedena pro jednoduchost jediná metoda
Interface IPožadované{
public void metoda1();
}
//Adaptér, jenž implementuje požadovanou metodu
public class Adapter implements IPožadované
{
public void metoda1(){
throw new UnsupportedOperationException();
}
}
//V adaptované třídě deklarujeme dědičnost na adaptéru
public class Třída1 extends Adapter{}
Adaptér obsahující adaptovaný objekt
Při tomto způsobu implementace již adaptér není definován jako univerzální, nýbrž jako jednoúčelový. Výhoda a hlavní důvod použití této implementace je ten, že není třeba zasahovat do adaptované třídy. Adaptér je definován jako třída implementující požadované rozhraní a jeho instance si uchovávají odkaz na adaptované třídy ve svých atributech. Klient se tedy obrací na adaptovanou třídu skrze adaptér, který teprve převádí klientovy požadavky adaptované třídě. Pomocí tohoto adaptéru, jenž obsahuje odkaz na adaptovanou třídu lze mimo instance této třídy obsloužit také všechny instance jejích potomků. Příkladem takto definovaného adaptéru jsou ony obalové třídy, které obalí hodnotu primitivního typu jako atribut instance některého z potomků třídy Object. Samozřejmě existují i spousty dalších příkladů...
Kostra implementace:
//požadované rozhraní
Interface IPožadované{
public void metoda1(String text);
}
// adaptovaná třída
public class AdaptovanáTřída{
public void čiňSe(){System.out.println(“Vypiš text”)}
}
public class Adapter implements IPožadované{
//atribut odkazující na adaptovanou třídu
private AdaptovanáTřída at;
public void metoda1(String s){
at.čiňSe();
System.out.println(“...adaptovaný výpis.”);
}
}
Adaptér jako potomek adaptované třídy
Při této definici adaptéru je potřeba zařídit, aby byl adaptér potomkem adaptované třídy a zároveň implementoval požadované rozhraní. Všechny metody požadované rozhraním jsou pak definovány přes volání metod adaptované třídy. Takto implementovaný adaptér je již čistě jednoúčelový, jelikož neumožňuje ani potomkům adaptované třídy, aby využili jeho služeb (na rozdíl od předchozí implementace). Tento způsob se využívá v situaci, kdy je potřeba začlenit instance třídy současně do dvou rámců, přičemž každý rámec vyžaduje implementaci jiného rozhraní. Řešením bude, že třída implementuje jedno rozhraní a druhé rozhraní implementuje adaptér, jenž bude jejím potomkem. Instance adaptéru pak mohou vystupovat v obou rámcích a budou splňovat implementaci obou rozhraní.
Kostra implementace:
//Požadované rozhraní
interface IPožadované{
public void metoda1();
}
//adaptovaná třída
public class AdaptovanáTřída{
}
//adaptér implementující požadované rozhraní a zároveň je potomkem adaptované třídy
public class Adapter extends AdaptovanáTřída implements IPožadované {
public void metoda1();
}
Shrnutí
- Návrhový vzor adaptér použijeme, pokud potřebujeme, aby třída měla jiné rozhraní než to, které právě má.
- Adaptér slouží jako prostředník mezi prostředím, které požaduje nějaké rozhraní, a třídou, jejíž rozhraní neodpovídá požadovanému. Umožňuje tedy spolupráci třídám, které by spolu jinak nespolupracovaly,
- Návrhový vzor Adaptér bývá označován jako Obal (wrapper).
- Adaptér je možno implementovat třemi způsoby:
- Jako společného rodiče adaptovaných tříd. Používá se při definici nových tříd, které nemusí být zařazeny do žádného systému dědičnosti.
- Jako třídu implementující požadované rozhraní , jejíž instance mají ve svém atributu uložen odkaz na adaptovanou instanci. Používá se při adaptaci původní adaptované třídy a libovolného z jejích potomků.
- Jako třídu implementující požadované rozhraní, která je současně potomkem adaptované třídy. Toto řešení se používá v případě, kdy je potřeba, aby instance splňovala implementaci jak původního, tak nově požadovaného rozhraní.
Literatura
- Pecinovský, Rudolf. 2007. Návrhové vzory. 1. vyd. Brno: Computer Press, a.s., 2007. ISBN 978-80-251-1582-4.