Adapter – strukturalny wzorzec projektowy

[1] Przeznaczenie

Adapter (ang. Adapter pattern, Wrapper) – jest jednym ze strukturalnych wzorców projektowych, którego zadaniem jest umożliwienie współpracy dwóm klasom o niekompatybilnych interfejsach. Adapter przekształca interfejs jednej z klas na interfejs drugiej klasy. Inną nazwą tego wzorca jest Wrapper, który oznacza opakowanie. Nazwa ta bardzo dobrze opisuję rolę jako pełni Adapter, a mianowicie ma pełnić wobec Klienta rolę otoczki, która umożliwia przetłumaczenie jego żądań na protokół zrozumiały dla faktycznego wykonawcy tych poleceń.

[2] Uzasadnienie stosowania

Dopasowanie istniejącego obiektu (nad którym programista nie ma kontroli) do określonego sposobu wywołania. Adapter umożliwia pracę klasom o niekompatybilnych interfejsach.

Wzorzec adaptera stosowany jest najczęściej w przypadku, gdy wykorzystanie istniejącej klasy jest niemożliwe ze względu na jej niekompatybilny interfejs. Drugim powodem użycia może być chęć stworzenia klasy, która będzie współpracowała z klasami o nieokreślonych interfejsach

[3] Stosowalność

Wzorzec projektowy Adapter może okazać się użyteczny w przypadku:

  • kiedy wykorzystanie istniejącej klasy jest niemożliwe ze względu na jej niekompatybilny interfejs,
  • chcemy stworzyć klasę, która będzie współpracowała z klasami o nieokreślonych interfejsach.

Zastosowanie wzorca projektowego Adapter pozwala m.in. na:

  • przekształcenie interfejsu klasy do innego interfejsu, którego Klient oczekuje,  Adapter pozwala klasom współpracować ze sobą, które nie mogą w inny sposób realizować tej współpracy, ponieważ mają różne interfejsy,
  • „opakowanie” istniejącej klasy w nowy interfejs,
  • przy pozornym oporze na dopasowanie starego komponentu do nowego systemu.

[4] Struktura

Interfejs jest definiowany w abstrakcyjnej klasie Target, a jego funkcjonalność w podklasach. Metody klasy Adapter mogą dokonywać konwersji typów lub tworzyć nowe algorytmy określające funkcjonalność nie przewidzianą w adaptowanych klasach. Wywołanie metody Adaptee jest dokonywane przez trawersowanie związku.

[5] Uczestnicy

Target – definiuje specyficzny interfejs, którego oczekuje Klient.

Adaptee – to obiekt, który dostarcza żądaną przez Klienta funkcjonalność, jest jednak niezgodny co do typu z interfejsem Target.

Adapter – implementuje typ Target, a jego rola polega na przetłumaczeniu wywołania metody należącej do typu Target poprzez wykonanie innej metody (lub grup metod) w klasie Adapter, dokonuje translacji pomiędzy interfejsem Target a obiektem Adaptee.

Client – współpracuje z obiektem Adapter o akceptowalnym przez siebie interfejsie Target, jednocześnie wykorzystując funkcjonalność dostarczoną przez Adaptee.

[6] Współpraca

Wzorzec projektowy Adapter dostosowuje interfejs klasy Adaptee tak, by był zgodny z interfejsem klasy bazowej Adapter. Umożliwia to użytkownikowi wykorzystanie obiektu klasy Adaptee oraz instancji klasy Adapter.

[7] Konsekwencje

Zastosowanie wzorca Adapter pozwala dopasować istniejące obiekty do tworzonych struktur klas i uniknąć ograniczeń związanych z ich interfejsem.

Konsekwencje stosowania wzorca są różne w zależności od tego z jakim typem mamy do czynienia.

W przypadku typu klasowego możemy mówić o następujących konsekwencjach:

  • brak możliwości adaptowania klasy wraz z jej podklasami
  • możliwość przeładowania metod obiektu adaptowanego

Do konsekwencji stosowania adaptera obiektowego należą:

  • możliwość adaptacji klasy wraz z jej podklasami (związane jest to z wykorzystaniem składania obiektów)
  • możliwość dodawania nowej funkcjonalności
  • brak możliwości przeładowania metod obiektu adaptowanego

W obu przypadkach należy liczyć się z narzutem na wydajność – tym większym, im większa jest niekompatybilność interfejsów.

[8] Formalna specyfikacja (język Z)

[9] Implementacja

Koncepcja wzorca Adapter jest bardzo prosta: piszemy klasę, która posiada wymagany interfejs, a następnie zapewniamy jej komunikację z klasą, która ma inny interfejs.

1.       Implementacja poprzez dziedziczenie

W tym przypadku z klasy, która ma niezgodny interfejs wywodzimy klasę pochodną oraz dopisujemy nowe metody w taki sposób, aby uzyskać wymagany interfejs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class AdoptowanaKlasa {
public:
void SpecyficzneZadanie();
}
class Adapter: private AdoptowanaKlasa, public Cel {
public:
Adapter():
AdoptowanaKlasa();
virtual void Zadanie() {
SpecyficzneZadanie()
}
}
class Cel {
virtual void Zadanie();
}

1.       Implementacja poprzez kompozycję

W tym przypadku zawieramy pierwotną klasę wewnątrz nowej i tworzymy metody wymaganego interfejsu realizujące wywołania metod klasy wewnętrznej.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class AdoptowanaKlasa {
public:
void SpecyficzneZadanie();
}

class Cel {
public:
void virtual Zadanie();
}

class Adapter: public Cel {
private:
AdoptowanaKlasa *adoptowana;
public:
Adapter(AdoptowanaKlasa *adoptowana) {
MyAdoptowana = adoptowana;
}
void Zadanie {
MyAdoptowana -> SpecyficzneZadanie();
}
}

Porównując te dwie metody dochodzimy do wniosku, że implementacja za pomocą wielokrotnego dziedziczenia (wywołanie metody dziedziczonej) możliwa jest oczywiście gdy implementacja następuje w językach wspierających tego typu funkcjonalność´. Implementacja za pomocą asocjacji jest bardziej uniwersalna (wywołanie metody skojarzonego obiektu).

[10] Przykład zastosowania

Typowym przykładem zastosowania adaptera są różnego rodzaju przejściówki do wtyczek o innej ilości bolców niż gniazdko, do którego chcemy coś wpiąć.

Dobrym przykładem zastosowania adaptera ‘z życia’ jest klucz nasadowy. Typowe wtyczki (wg standardu US) mają rozmiar 1/2 cala i 1/4 cala. Oczywiście wtyczka w rozmiarze 1/2 cala nie może zostać podłączona do gniazda 1/4 cala, jest to możliwe jedynie przy użyciu adaptera. Dopiero gniazdo połączone z odpowiednią przejściówką zapewnia wpięcie właściwej wtyczki.

Spójrzmy jeszcze na przykład programistyczny.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include
#include

class Adapter
{
public :
virtual ~Adapter();

virtual int get25() const;

protected :
virtual int get25_() const = 0;
};

Adapter::~Adapter()
{ /*  */ }

int Adapter::get25() const
{
int retval(get25_());
assert(retval == 25);
return retval;
}

namespace Details
{
template < typename AdapteeType >
struct AdapteeTraits
{
static int get25(const AdapteeType * adaptee)
{
return adaptee->get25();
}
};
}

namespace Details
{
template < typename AdapteeType >
class Adapter : public ::Adapter
{
public :
Adapter(AdapteeType * adaptee);
protected :
int get25_() const;

private :
AdapteeType * adaptee_;
};

template < typename AdapteeType >
Adapter< AdapteeType >::Adapter(AdapteeType * adaptee)
: adaptee_(adaptee)
{ /* */ }

template < typename AdapteeType >
int Adapter< AdapteeType >::get25_() const
{
return AdapteeTraits< AdapteeType >::get25(adaptee_);
}
}

struct Adaptee
{
int get25() const
{
return 26;
}
};

int main()
{
Adaptee adaptee;
std::auto_ptr< Adapter > adapter(new Details::Adapter< Adaptee >(&adaptee));
return adapter->get25();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class CdPlayer{public:void playButtonPushed(Button*)
{cout << "PLAY" << endl;}
void stopButtonPushed(Button*)
{cout << "STOP" << endl;}}; class CdPlayerAdapter:  public ButtonClient { public:  CdPlayerAdapter(CdPlayer* pCd_): _pCd(pCd_) {}  /*virtual*/  void buttonPushed(Button* pButton_)    {     if (pButton_ == _pPlayButton) _pCd->playButtonPushed(pButton_);
else if (pButton_ == _pStopButton) _pCd->stopButtonPushed(pButton_);
}
void setPlayButton(Button* pButton_)
{_pPlayButton = pButton_;}
void setStopButton(Button* pButton_)
{_pStopButton = pButton_;}
private:
Button* _pPlayButton;
Button* _pStopButton;
CdPlayer* _pCd;};

main(){
CdPlayer aCdPlayer;
CdPlayerAdapter aCdPlayerAdapter(&aCdPlayer);
Button playButton(&aCdPlayerAdapter);
Button stopButton(&aCdPlayerAdapter);
aCdPlayerAdapter.setPlayButton(&playButton);
aCdPlayerAdapter.setStopButton(&stopButton);
playButton.push();
stopButton.push();
return 0;
}

[11] Zadanie

Mamy 2 klasy – linię i prostokąt. Chcemy narysować linię od(10,20) do (30,60)

I prostokąt od (10,20) o dlugosci 20 I wysokości 40. W podanym w pliku zadaniu użytkownik musi za każdym razem podawać typ kształtu, który chce uzyskać i odpowiednio podawać wtedy argumenty. Zmodyfikuj program tak nie było to konieczne.

Podpowiedź:

Dodaj interfejs i odpowiednie klasy, które się tym zajmą.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include

typedef int Coordinate;
typedef int Dimension;

class Rectangle {
public:
virtual void draw() = 0;
};

class LegacyRectangle {
public:
LegacyRectangle( Coordinate x1, Coordinate y1,
Coordinate x2, Coordinate y2 ) {
x1_ = x1;  y1_ = y1;  x2_ = x2;  y2_ = y2;
cout << "LegacyRectangle:  create.  (" << x1_ << "," << y1_
<< ") => (" << x2_ << "," << y2_ << ")" << endl; }
void oldDraw() {
cout << "LegacyRectangle:  oldDraw.  (" << x1_ << "," << y1_
<< ") => (" << x2_ << "," << y2_ << ")" << endl; }
private:
Coordinate x1_;
Coordinate y1_;
Coordinate x2_;
Coordinate y2_;
};

class RectangleAdapter : public Rectangle, private LegacyRectangle {
public:
RectangleAdapter( Coordinate x, Coordinate y, Dimension w, Dimension h )
: LegacyRectangle( x, y, x+w, y+h ) {
cout << "RectangleAdapter: create.  (" << x << "," << y
<< "), width = " << w << ", height = " << h << endl; }
virtual void draw() {
cout << "RectangleAdapter: draw." << endl;       oldDraw(); } }; void main() {    Rectangle*  r = new RectangleAdapter( 120, 200, 60, 40 );    r->draw();
}

// LegacyRectangle:  create.  (120,200) => (180,240)
// RectangleAdapter: create.  (120,200), width = 60, height = 40
// RectangleAdapter: draw.
// LegacyRectangle:  oldDraw.  (120,200) => (180,240)

[12] Bibliografia

1.       Gamma E., Helm R., Johnson R., Vlissides J.: Design Patterns. Elements of Reusable Object-Oriented Software, wyd. Addison-Wesley, 1998

2.       Instytut Informatyki Politechniki Warszawskiej: Inżynieria oprogramowania 2 – Wzorce projektowe – Adapter [dostęp: 24 kwietnia 2009].

3.       Vince Huston: Design Patterns – Adapter [dostęp 24 kwietnia 2009].

4.       Beata Frączek: Wzorzec projektowy Adapter [dostęp 24 kwietnia 2009].

5.       Amnon H Eden, Jonathan Nicholson, Epameinodas Gasparis: Adapter (Class) Design Pattern [dostęp 24 kwietnia 2009]

Leave a Comment

seven + 8 =