background image

34

Inżynieria

oprogramowania

www.sdjournal.org

 Software Developer’s Journal   04/2007

Ewolucja wzorca polimorfi zmu 
zewnętrznego w C++

J

ęzyk pretendując do określania się mianem ję-
zyka obiektowego winien wspierać paradyg-
maty obiektowości – w tym polimorfi zmu. W ję-

zyku C++ owo wsparcie mamy zapewnione w dwóch 
odmianach: statycznej (czasu kompilacji) i dynamicz-
nej (czasu wykonania).

Statyczna odmiana polimorfi zmu dostępna po-

przez mechanizm przeciążania funkcji i operatorów 
ze względu na typy argumentów, pozwala na trakto-
wanie symbolu funkcji jako obiektu polimorfi cznego. 
Mechanizm ten jest rozszerzony na pozostałe ele-
menty języka dzięki możliwościom, które daje pro-
gramiście system szablonów. Moc polimorfi zmu sta-
tycznego pokazuje programowanie generyczne sto-
sowane w bibliotece standardowej STL, które łączy 
szybkość i optymalność z dobrodziejstwem technik 
obiektowych, właśnie dzięki zastosowaniu polimorfi -
zmu statycznego.

Na przełomie 1994 i 1995 niemalże przez przy-

padek odkryto, że system szablonów języka C++ jest 
kompletny w rozumieniu Turinga, a co za tym idzie po-
zwala na zapis dowolnego algorytmu w tym: rozwią-
zywanie rekursywnych warunków, generowanie pro-
gramów w czasie kompilacji itp. Ta własność syste-
mu szablonów doprowadziła do burzliwego rozwo-
ju dziedziny o nazwie metaprogramowanie za pomo-
cą szablonów (ang. template mataprogramming). Do-
głębny opis tej problematyki można znaleźć w książce 
[Abra04], którą polecam wszystkim zainteresowanym.

Polimorfi zm dynamiczny w języku C++ wiąże się 

z jednej strony z możliwościami odwołania się do kla-
sy potomnej poprzez interfejs klasy bazowej za po-
mocą mechanizmu RTTI (ang. Run-Time-Type -In-
formation),
 a z drugiej strony z mechanizmem metod 
wirtualnych realizowanych przez tablicę vtable.. Do-
głębne omówienie tego tematu znajduje się w pozy-
cji [Lipp96] napisanej przez jednego z twórców języ-
ka C++.

W pracy [Clee97] w 1997 roku zaprezentowano 

wzorzec projektowy o nazwie Polimorfi zm Zewnętrz-
ny  (ang. External Polymorphism), którego głównym 
zadaniem było umożliwienie klasom w żaden sposób 
nie  skojarzonych  ze  sobą  na  wzajemną  współpracę 
poprzez zautomatyzowanie procesu adoptowania in-
terfejsów do siebie. Automatyzował on wzorzec pro-

jektowy Adapter opisany przez „gang czterech”, pio-
nierów w dziedzinie wzorców projektowych w inży-
nierii oprogramowania [Gamm95]. W tym artykule 
zostanie przedstawiony wzorzec Polimorfi zmu  Ze-
wnętrznego, jego ewolucja i odmiany.

Problem wytworzenia interfejsu użytkownika dla 

programu  odgrywającego  muzykę  jest  na  tyle  czy-
telny i intuicyjny, że wydaje się ręcz idealnym przy-
kładem do demonstracji wzorca Polimorfi zmu  Ze-
wnętrznego. Otóż: dysponując klasą 

MP3Player

, któ-

ra  udostępnia  funkcjonalność  odgrywania  i  zatrzy-
mywania bieżącego utworu za pomocą metod 

play

stop

, stajemy przed problemem podłączenia ele-

mentów grafi cznego interfejsu użytkownika składają-
cego się z dwóch przycisków.

Każdy z przycisków w odpowiedzi na interakcję 

użytkownika generuje zdarzenie, które należy ob-
służyć poprzez wywołanie odpowiedniej metody kla-
sy 

Mp3Player

.

W pierwszym podejściu do tego problemu zasto-

sujemy wzorzec Adapter.

Wzorzec Adapter [Gamm95] opisuje sposób ada-

ptacji interfejsu klasy Adaptowanej (ang. Adaptee) – 
do postaci akceptowalnej przez interfejs klasy Klienc-
kiej  (ang. Client). Pozwala na współpracę klas, które 
nie mogą ze sobą współpracować bezpośrednio ze 
względu na wzajemną niekompatybilność interfejsów.

Scenariusz, na którym wzorzec Adapter opiera 

swoje działanie, można streścić następująco: obiekt 
klasy Klienckiej wywołuje operację na obiekcie Ada-
ptera, poprzez interfejs Celu (ang. Target) pozwalają-
cy na takowe wywołanie. Wewnętrzna implementa-
cja Adaptera oddelegowuje z kolei wywołanie do kla-
sy adaptowanej (ang. Adaptee).

Wewnątrz wzorca Adapter zawarte jest niejawne 

założenie, iż interfejsy zarówno obiektu Klienta jak 
i Celu możemy podzielić na dwie części – udostęp-
nianą (ang. provided) i wymaganą (ang. required). To 
założenie okazuje się również prawdziwe dla każ-
dego dobrze zdefi niowanego interfejsu komponen-
tu. Część udostępniana – interfejs udostępniany jest 
postrzegana jako główna część interfejsu kompo-
nentu, pozwala na dostęp do podstawowych funk-
cjonalności udostępnianych przez komponent. Dru-
ga część – interfejs wymagany – jest zbiorem zało-
żeń, które komponent mniej lub bardziej jawnie robi 
na rzecz użytkownika (ang. user) swoich funkcjonal-
ności. Komponent może np.: wymagać stworzenia 
i/lub zniszczenia swojej instancji w środowisku, dla 
którego został zaprojektowany, może wymagać by 
komponenty używające udostępnionych przez niego 
funkcjonalności – użytkownicy danego komponentu - 

Paweł Kapłański

Autor jest architektem wiodącym w korporacji między-
narodowej zajmującej się m.in. systemami wbudowany-
mi (ang. embedded).
Kontakt z autorem: pawel.kaplanski@wp.pl
Kody żródłowe: http://www.digital-advanced.com

background image

Ewolucja wzorca polimorfi zmu zewnętrznego w C++

35

www.sdjournal.org

Software Developer’s Journal   04/2007

wykonywali metody części udostępnionej interfejsu w określo-
nej kolejności. Co więcej, może wymagać tego, aby użytkow-
nicy potrafi li w określony sposób reagować na generowane 
przez niego zdarzenia.

W przypadku wzorca Adapter zdarzeniowa część interfej-

su wymaganego komponentu klienta realizowana jest za po-
mocą interfejsu Celu. Możemy na ową parę Klient-Cel patrzeć 
jako na interfejs dualny pojedynczego komponentu. Wyraźny 
podział na część funkcjonalną i zdarzeniową implikuje imple-
mentację za pomocą konkretnej klasy Adaptera, zdarzeniowej 
części interfejsu wymaganego.

Powróćmy do analizowanego przykładu. Zastanówmy się 

nad użyciem wzorca Adapter do rozwiązania przedstawio-
nego tam problemu. Załóżmy, że dysponujemy klasą 

Button

 

oraz 

ButtonClient

, które to pełnią rolę owej pary interfejsów 

pojedynczego komponentu. Identyfi kujemy 

Button

 z Klientem 

ButtonClient

 z interfejsem Celu (Listing 1).

Obiekty klasy 

Button

 wymagają, by podać w ich konstruk-

torze odpowiednią implementację interfejsu 

ButtonClient

, któ-

ra to udostępni implementację wirtualnej metody 

onPush

 wy-

woływanej przez 

Button

 w reakcji GUI na interakcję z użyt-

kownikiem. Implementacja ta jest konkretną implementacją in-
terfejsu wymaganego komponentu Button-ButtonClient.

Aby mieć możliwość przeprowadzenia symulacji działa-

nia naszego rozwiązania, wyposażmy dodatkowo klasę 

Button

 

w funkcję 

Button::simulatePush

, która umożliwi zasymulowa-

nie wciśnięcia przycisku poprzez odwołanie się do zdarzenio-
wej części interfejsu wymaganego – interfejsu Celu – w tym 
przypadku abstrakcyjnej klasy 

ButtonClient

.

Klasą Adaptowaną (ang. Adaptee) w naszym przypadku 

jest klasa 

Mp3Player

, której implementacja składa się z dwóch 

metod – 

play

 i 

stop

 (Listing 2).

Pozostaje nam jedynie implementacja Adapterów, które 

to używając interfejsu Celu wywołają odpowiednie metody na 
obiekcie klasy Adaptowanej (Listing 3).

Zwróćmy uwagę, że implementacja Adaptera dla przyci-

sku Stop różni się od implementacji adaptera przycisku Play 
jedynie implementacją metody interfejsowej 

ButtonClient::on-

Push

. (Listing 4).

Uwieńczmy nasze rozwiązanie małym programem testu-

jącym, konfi gurującym poszczególne elementy rozwiązania 
i spinającym je w całość (listing 5).

Polimorfi zm zewnętrzny

Jak wyżej wspomniano, implementacja Adapterów obu przy-
cisków jest analogiczna, wydaje się więc naturalne, aby w pe-
wien sposób zautomatyzować ich wytwarzanie. Dokonać tego 
możemy przy użyciu systemu szablonów wytwarzając gene-
ryczny Adapter, który konkretyzowany z odpowiednim typem 
i wskaźnikiem na metodę może generować automatycznie od-
powiedni Adapter.

Rysunek 1. 

Rozważany problem wytworzenia interfejsu 

użytkownika

Mp3Player

+play()
+stop()

?

Rysunek 2. 

Schemat wzorca projektowego Adapter

Client

Adapter

+Request()

Adaptee

+SpecificRequest()

<<

interfejs

>>

Target

+Request()

adaptee->SpecificRequest()

1

Listing 1. 

Elementy interfejsu komponentu przycisku

class

 

ButtonClient

{

public

:

 

virtual

 

void

 

onPush

()

 

=

 0

;

}

;

class

 

Button

{

public

:

 

explicit

 

Button

(

ButtonClient

*

 

client

)

 

  

:

 

client_

(

client

)

 

{}

...

//funkcja pozwalajaca na symulacje wcisniecia przycisku

 

void

 

simulatePush

()

 

{

  

client_

->

onPush

();

 

}

private

:

 

ButtonClient

*

 

client_

;

}

;

Listing 2. 

Klasa Mp3Player

#include 

<iostream>

class

 

Mp3Player

{

public

:

 

void

 

play

()

 

{

std

::

cout

 

<<

 

"PLAY"

 

<<

 

std

::

endl

;

}

 

void

 

stop

()

 

{

std

::

cout

 

<<

 

"STOP"

 

<<

 

std

::

endl

;

}

}

;

background image

36

Inżynieria

oprogramowania

www.sdjournal.org

 Software Developer’s Journal   04/2007

Tak zautomatyzowane podejście jest podstawą wzorca 

polimorfi zmu  zewnętrznego.  Szablon  generycznego  Adapte-
ra dla interfejsu 

ButtonClient

 konkretyzowany z typem klasy 

Adaptowanej, w konstruktorze pobierze wskaźnik na obiekt 
Adaptowany oraz wskaźnik na metodę. Wskaźnik na ową me-
todę zostanie użyty w automatycznie wygenerowanej imple-
mentacji metody interfejsu Celu – 

ButtonClient::onPush

, któ-

ra winna być wołana w odpowiedzi na zdarzenie generowane 
przez obiekt klasy Klienckiej – 

Button

 (Listing 6).

Ta zmiana pociąga za sobą modyfi kację naszego progra-

mu testowego. Główna jego część pozostanie bez zmian, lecz 
nie potrzebujemy już redundantnych implementacji Adapte-
rów. Konkretyzujemy je dopiero w momencie rzeczywistej po-
trzeby, w naszym przypadku z klasą Mp3Playera, podając od-
powiednie wskaźniki do metod (Listing 7).

Generyczny Adapter może być postrzegany jako konstrukt 

pozwalający na polimorfi czne używanie klasy Adaptowanej 
bez ingerencji w samą strukturę owej klasy, stając się swo-
istym pryzmatem, przez który możemy patrzeć na ową klasę. 
Innej mówiąc: możemy uważać taki generyczny Adapter za 
rodzaj polimorfi cznego interfejsu klasy Adaptowanej, który po-
zwala dowolnemu źródłu na używanie tej klasy bez potrzeby 
jej modyfi kacji. Stąd wynika nazwa wzorca projektowego Ze-
wnętrznego Polimorfi zmu.

Następnym krokiem w ewolucji wzorca Zewnętrznego Po-

limorfi zmu jest zautomatyzowanie Generycznego Adaptera do 
tego stopnia, aby jego postać nie była zależna ani od interfej-
su Adaptowanego, ani od komponentu używającego Adapte-
ra do zgłaszania zdarzeń, lub mówiąc inaczej: ugenerycznie-

nie również interfejsu Celu. Takowy Generyczny Adapter nie-
jako delegowałby wywołanie metody w kontekście pewnego 
obiektu, dlatego nazywamy go Delegatem (

Delegate

). Delegat 

powinien służyć do adaptowania dowolnych metod – o różnej 
liczbie i typach argumentów - w tym również metod statycz-
nych, my jednak zajmiemy się szczegółowo przypadkiem me-
tod niestatycznych.

Pracę rozpocznijmy od stworzenia szablonu generyczne-

go interfejsu klasy Celu (Listing 8).

Listing 3. 

Implementacja adaptera dla przycisku Play

class

 

Mp3PlayerPlayButtonAdapter

 

 

:

 

public

 

ButtonClient

{

public

:

 

explicit

 

Mp3PlayerPlayButtonAdapter

(

Mp3Player

*

 

player

)

  

:

 

player_

(

player

)

 

{}

 

virtual

 

void

 

onPush

()

 

{

  

player_

->

play

();

 

}

private

:

 

Mp3Player

*

 

player_

;

}

;

Listing 4. 

Implementacja adaptera dla przycisku Stop

class

 

Mp3PlayerStopButtonAdapter

 

 

:

 

public

 

ButtonClient

{

...

 

virtual

 

void

 

onPush

()

 

{

  

player_

->

stop

();

 

}

...

}

;

Listing 5. 

Program testujący

void

 

main

(

char

*

 

args

[]

int

 

argc

)

{

 

Mp3Player

 

aMp3Player

;

 

 

Mp3PlayerPlayButtonAdapter

 

playButtonClient

(&

aMp3Player

);

 

Mp3PlayerStopButtonAdapter

 

stopButtonClient

(&

aMp3Player

);

 

Button

 

playButton

(&

playButtonClient

);

 

Button

 

stopButton

(&

stopButtonClient

);

// symulacja wciskania przyciskow

 

playButton

.

simulatePush

();

 

stopButton

.

simulatePush

();

}

Listing 6. 

Generyczny Adapter przycisku

template

<

class

 

T

>

class

 

ExternallyPolymorphicButtonClient

 

:

 

public

 

ButtonClient

{

public

:

 

ExternallyPolymorphicButtonClient

(

T

*

 

t

,

void

 

(

T

::*

mth

)())

  

:

 

t_

(

t

)

,

mth_

(

mth

)

{}

;

 

virtual

 

void

 

onPush

()

 {

  

(

t_

->*

PtrToMth

)();

 

}

private

:

 

T

*

 

t_

;

 

void

 

(

T

::*

mth_

)();

}

;

Listing 7. 

Automatycznie generowane Adaptery 

przycisków

void

 

main

(

char

*

 

args

[]

,

int

 

argc

)

{

...

 

ExternallyPolymorphicButtonClient

<

Mp3Player

>

 

playButtonCli

ent

(&

aMp3Player

,

&

Mp3Player

::

play

);

  

 

ExternallyPolymorphicButtonClient

<

Mp3Player

>

 

stopButtonCli

ent

(&

aMp3Player

,

&

Mp3Player

::

stop

);

...

}

background image

Ewolucja wzorca polimorfi zmu zewnętrznego w C++

37

www.sdjournal.org

Software Developer’s Journal   04/2007

Generyczny Adapter, implementujący interfejs generycz-

nego Celu powinien mieć dostęp do klasy adaptowanej, więc 
szablon, za pomocą którego zrealizujemy ów konstrukt będzie 
miał dodatkowy parametr odpowiadający owej Adaptowanej 
klasie (Listing 9).

Sama w sobie klasa Delegata będzie zarządzała życiem 

generycznego Adaptera pozwalając na jego naturalne używa-
nie poprzez semantykę kopiowania, dodatkowo wyposażając 
go w operator wywołania funkcji (Listing 10).

Aby uprościć syntaktycznie użycie delegatów wspomo-

żemy się mechanizmem automatycznej dedukcji typów ar-
gumentów szablonów funkcji. Szablony funkcji mają tą wła-

sność, iż argumenty szablonu nie muszą być podawane jaw-
nie. Kompilator może je wydedukować z argumentów funkcji. 
Tak, więc zamiast

int i,j,k;

k=max<int>(i,j);

możemy napisać

int i,j,k;

k=max(i,j)

Kompilator dopasuje sygnatury funkcji do parametrów jej wy-
wołania i automatycznie dokona odpowiedniej konkretyzacji – 
w tym przypadku przyjmie jako parametr typ 

int

Taki narzędziowy szablon funkcji nazwiemy 

delegate _

cast

. Będzie on zwracał delegata wypełnionego odpowiednią 

implementacją przyjmując za parametry: szablon, który obsłu-
guje, wskaźnik na adaptowany obiekt oraz odpowiednią meto-
dę tego obiektu, do której będzie odbywać się delegacja wy-
wołania (Listing 11).

Wróćmy do przykładu. Implementacja upraszcza się 

w stopniu zaskakującym. Patrz Listing 12.

Zwróćmy uwagę, że nie ma już potrzeby tworzenia kla-

sy interfejsowej 

ButtonClient

. Zamiast niej specyfi kujemy 

miejsce podpięcia się do samej klasy 

Button

 poprzez użycie 

obiektu 

Delegat

 w postaci publicznego atrybutu. Co więcej, 

nie mamy potrzeby tworzenia odpowiednich Adapterów. Za-
miast tego podpinamy się bezpośrednio do obiektu klasy 

But-

ton

 z funkcjonalnościami udostępnianymi przez klasę 

Mp3Play-

er

. Warto zauważyć, że ciało metody 

Button::simulatePush

 po-

sługuje się zdefi niowanym w klasie 

Delegate

 operatorem wy-

wołania funkcji, przez co wygląda jak oddelegowanie wywo-
łania do innej metody, gdy tym czasem w rzeczywistości uru-
chamiają cały mechanizm Delegatów. Takie uruchomienie bę-
dziemy nazywali Odpaleniem Delegata.

Rysunek 3. 

Rozwiązanie przykładowego problemu z pomocą wzorca Adapter

Button

Mp3Player

+play()
+stop()

<<interfejs>>

ButtonClient

+onPush()

-client

-player

-player

Mp3PlayerPlayButtonAdapter

+onPush()

player->play()

player->play()

Mp3PlayerStopButtonAdapter

+onPush()

1

1

Rysunek 4. 

Generyczny Adapter przycisku

ExternallyPolymorphicButtonClient<Mp3Player>

-mth_=&Mp3Player::stop

ExternallyPolymorphicButtonClient<Mp3Player>

-mth_=&Mp3Player::play

ExternallyPolymorphicButtonClient

-T* t_
- void (T::*mth_) ()

+onPush()

Mp3Player

+play()
+stop()

Button

<<

interfejs

>>

ButtonClient

+onPush()

(t_->*mth_)()

T

1

1

-client

background image

38

Inżynieria

oprogramowania

www.sdjournal.org

 Software Developer’s Journal   04/2007

Mechanizm metod wirtualnych wyposażając C++ we 

wsparcie dla polimorfi zmu dynamicznego, pozwolił na wyeli-
minowanie wielu konstrukcji składających się z garści rozga-
łęzień logicznych typu 

if

/

else if

/

else

 lub 

swich(...){...}

 po 

znaczniku typu. Spowodowało to znaczne zwiększenie zro-
zumiałości algorytmów. Mając do dyspozycji Delegaty wypo-
sażajmy zestaw naszych narzędzi w polimorfi zm  zewnętrz-
ny, pozwalając na znaczne rozluźnienie wewnętrznych relacji 
interfejsów miedzyobiektowych. W ten sposób udało nam się 
wzbogacić język narzędzi podstawowych o bardzo silne na-
rzędzie pozwalające na separację interfejsów.

Aby się o tym przekonać zobaczmy jak wygląda doda-

nie funkcjonalności prezentacji bieżącej pozycji odtwarzane-
go utworu na odtwarzaczu mp3. Dodajmy do zbioru kontrolek 
klasę 

ProgressBar 

(Listing 13).

Klasa ta umożliwia wizualizację postępu dowolnej operacji 

w postaci prostokąta wypełnionego w proporcjonalnym stop-
niu bazując na postępie podanym w metodzie 

ProgressBar::

setProgress

.

Wzbogaćmy klasę 

Mp3Player

 w dodatkowego Delegata, 

który odpala się z taką informacją, gdy bieżąca pozycja od-
twarzanego utworu ulega zmianie (Listing 14).

Uważny  czytelnik  zauważy,  że  metoda 

Mp3Player::simu-

lateProgress

 używa Delegata 

Mp3Player::eventProgress

 ja-

ko  funktora  podawanego  do  algorytmu  standardowego 

std::

for _ each

. Kompatybilność Delegatów ze standardowymi al-

gorytmami jest możliwa właśnie dzięki przeciążeniu operato-
ra wywołania funkcji, a co za tym idzie konkretny Delegat jest 
funktorem w rozumieniu biblioteki STL.

Powróćmy  jeszcze  na  chwilę  do  sprawy  podziału  inter-

fejsu komponentu na dwie części – część udostępnianą 

Listing 9. 

Ogólny generyczny Adapter 

template

<

class

 

T

typename

 

RetVal

typename

 

Arg1

typename

 

Arg2

,... 

>

  

class

 

DelegateImpl

 

:

 

  

public

 

virtual

 

IDelegate

<

RetVal

Arg1

Arg2

Arg3

Arg4

Arg5

>

  

{

  

public

:

                         

  

typedef

 

typename

 

ICallable

<

RetVal

Arg1

Arg2

,...

>

    

::

MethodType

<

T

>::

V

 

methodType

;

  

DelegateImpl

 

(

T

*

 

obj

methodType

 

method

)

 

  

:

 

obj_

(

obj

)

 , 

method_

(

method

)

 

{}

  

RetVal

 

call

(

Arg1

 

arg1

Arg2

 

arg2

, ...

)

 

const

  

{

  

return

 

(

obj_

->*

method_

)(

arg1

arg2

, ...

);

 

}

protected

:

  

methodType

 

method_

;

  

T

 

*

obj_

;

}

;

   

Listing 10. 

Implementacja Delegata

template

<

typename

 

RetVal

typename

 

Arg1

typename

 

Arg2

,...

>

  

class

 

Delegate

  

{

  

public

:

  

explicit

 

Delegate

(

IDelegate

<

RetVal

,

Arg1

,

Arg2

,

Arg3

,

Arg4

,

Ar

g5

>*

 

ptr

=

0

)

  

:

 

ptr_

(

ptr

)

{}

...

  

RetVal

 

operator

()(

Arg1

 

arg1

Arg2

 

arg2

)

 

const

  

{

  

return

 

ptr_

->

call

(

arg1

arg2

);

  

}

private

:

 

IDelegate

<

RetVal

,

Arg1

,

Arg2

,

Arg3

,

Arg4

,

Arg5

>*

 

ptr_

;

}

;

Listing 11. 

Pomocniczy narzędziowy szablon funkcji

template

<

template

 

<

typename

,

typename

,... 

>

 

class

 

O

class

 

T

typename

 

RetVal

typename

 

Arg1

,

typename

 

Arg2

,...

>

O

<

RetVal

Arg1

Arg2

>:

HandleType

 

delegate_cast

 

(

T

*

 

obj

RetVal

(

T

::*

methodType

)

(

Arg1

Arg2

,...

))

{

 

return

 

O

<

RetVal

Arg1

Arg2

>::

HandleType

<

RetVal

Arg1

Arg2

,...

>

(

new

 

O

<

RetVal

Arg1

Arg2

>

DelegateImlp

 

<

T

>

(

obj

methodType

));

}

Listing 8. 

Interfejs Celu jako szablon. 

template

<

typename

 

RetVal

typename

 

Arg1

typename

 

Arg2

,... 

>

class

 

ICallable

<

RetVal

Arg1

Arg2

,...

>

{

public

:

                         

 

ICallable

()

{}

 

virtual

 ~

ICallable

()

{}

 

virtual

 

RetVal

 

call

(

Arg1

 

arg1

Arg2

 

arg2

,...

)

 

const

 

=

 0

;

...

 

template

 

<

typename

 

T

>

 

struct

 

MethodType

 

 

{

  

typedef

 

RetVal

 

(

T

::*

V

)

 

(

Arg1

Arg2

);

  

}

;

  

typedef

 

RetVal

 

(*

StaticType

)

 

(

Arg1

Arg2

);

}

;

   

template

<

typename

 

RetVal

typename

 

Arg1

typename

 

Arg2

,... 

>

class

 

IDelegate

 

:

 

public

 

ICallable

<

RetVal

,

Arg1

,

Arg2

,...

>

{

public

:

 

virtual

 

bool

 

isEqual

(

const

 

IDelegate

<

RetVal

,

Arg1

,

Arg2

,...

>

&

 

h

)

 

const

 

=

 0

;

 

virtual

 

IDelegate

*

 

clone

()

 

const

 

=

 0

;

}

;

background image

39

Ewolucja wzorca polimorfi zmu zewnętrznego w C++

www.sdjournal.org

Software Developer’s Journal   04/2007

i część wymaganą. Interfejs korzystający z delegatów wy-
maga od swojego użytkownika, aby ten się do nich odpo-
wiednio przypiął, tworząc swoistego rodzaju wtyczkę na in-
terfejs innego komponentu. Co więcej - komponent z taką 
„wtyczką” nie może działać poprawnie, jeśli nikt się do owej 

Rysunek 5. 

Schemat Delegata

DelegateImpl

-T* obj_
-RetVal (T::*mth_) (Arg1, Arg2, Arg3, ...)
+RetVal()
+bool isEqua;()
+clone()

automatycznie generowana implementacja

delegat

abstrakcyjny interfejs delegata

T, RetVal, Arg1, Arg2, Arg3, ...

Delegate

+RetVal operator (Arg1, Arg2, Arg3,...)()
+bool operator==()
+operator=()

IDelegate

+RetVal call()
+bool isEqual()
+clone()

RetVal, Arg1, Arg2, Arg3, ...

RetVal, Arg1, Arg2, Arg3, ...

Listing 12. 

Implementacja przykładu przy użyciu 

Delegatów

class

 

Button

{

public

:

  

Delegate

<

void

>

 

eventPush

;

 

 // function used by 

  

Framework

 

to

 

generate

 

event

  

void

 

simulatePush

()

  

{

  

eventPush

();

  

}

  

}

;

  

void

 

main

(

char

*

 

args

[]

,

int

 

argc

)

  

{

  

Mp3Player

 

aMp3Player

;

  

Button

 

playButton

;

  

Button

 

stopButton

;

  

playButton

.

eventPush

=

delegate

  

_cast

<

Delegate

>(&

aMp3Player

  ,

&

Mp3Player

::

play

);

  

stopButton

.

eventPush

=

delegate

  

_cast

<

Delegate

>(&

aMp3Player

  ,

&

Mp3Player

::

stop

);

 

 // symulacja wcisniecia klawiszy

  

playButton

.

simulatePush

();

  

stopButton

.

simulatePush

();

}

Listing 13. 

Prosta kontrolka ProgressBar

class

 

ProgressBar

  

{

  

public

:

  

void

 

setProgress

(

int

 

position

)

  

{

  

std

::

cout

 

<<

 

"PROGRESS:"

<<

 

position

 

<<

 

std

::

endl

;

  

}

}

;

Rysunek 6. 

Scenariusz interakcji Klient-Serwer w przypadku 

Delegata

Server

Klient

request=onRequest

request()

response()

run

run

onRequest

background image

40

Inżynieria

oprogramowania

www.sdjournal.org

 Software Developer’s Journal   04/2007

wtyczki nie przypnie. Obiekt, którego interfejs posiada taką 
wtyczkę nazwijmy Serwerem, a obiekt używający tej wtycz-
ki Klientem.

Rozluźnienie powiązań miedzyobiektowych, które moż-

na uzyskać za jej pomocą pozwala na całkowite uniezależnie-

nie Klienta od Serwera i vice-versa. Spięcie obu komponen-
tów może odbywać się na zewnątrz, w części konfi guracyjnej 
systemu.

Rozgłaszanie zdarzeń

Podstawowy model interakcji pomiędzy Klientem a Serwerem 
możemy opisać następująco:

•   Klient jest przypinany do odpowiedniego Delegata wysta-

wionego przez Serwer;

•   Serwer Odpala Delegata;
•   Delegat przekazuje wywołanie do metody po stronie Klienta;
•   Metoda Klienta jest uruchomiona synchronicznie w wątku 

wołającego i po wykonaniu zwraca wartość poprzez Dele-
gata do Serwera.

W powyższym scenariuszu daje się zauważyć silne powią-
zanie jeden-do-jednego pomiędzy Klientem a Serwerem. 

Listing 14. 

Zdarzenie o zmianie pozycji w bieżącym 

utworze zaimplementowane jako Delegat

#include 

<algorithm>

#include 

<vector>

class

 

Mp3Player

{

public

:

  

Delegate

<

void

,

int

>

  

eventProgress

;

 

 // symulacja zmiany

  

pozycji

 

biezacego

 

utworu

  

void

 

simulateProgress

()

  

{

  

std

::

vector

<

int

>

 

positions

;

  

for

(

int

 

i

=

0

;

i

<=

100

;

i

+=

10

)

positions

.

push_back

(

i

);

  

std

::

for_each

(

positions

.

begin

()

,

positions

.

end

()

,

eventProgress

);

  

}

}

;

void

 

main

(

char

*

 

args

[]

,

int

 

argc

)

{

  

Mp3Player

 

aMp3Player

;

  

ProgressBar

 

progressBar

;

  

aMp3Player

.

eventProgress

=

  

delegate_cast

<

Delegate

>

  

(&

progressBar

,

&

ProgressBar

  

:

setProgress

);

 

aMp3Player

.

simulateProgress

();

}

Listing 15. 

Element cyklicznej listy dwukierunkowej 

template

<

int

 

n

>

class

 

DLListNode

{

public

:

 

DLListNode

<

n

>()

 

:

currentPtr_

(

0

)

 

{

  

next_

=

prev_

=

this

;

 

}

// automatyczne wypiecie z listy

 

virtual

 ~

DLListNode

<

n

>()

 

{

  

if

(

currentPtr_

!=

&&

 

*

currentPtr_

==

this

)

 

*

currentPtr_

=

prev_

;

  

prev_

->

next_

=

next_

;

  

next_

->

prev_

=

prev_

;

 

}

//wstawienie pomiedzy

nastepnego

 

a

 

poprzedniego

 

void

 

insert

(

DLListNode

<

n

>

*

 

elem

,

DLListNode

<

n

>**

 

currentPtr

)

 

{

  

elem

->

prev_

=

this

;

  

elem

->

next_

=

next_

;

  

elem

->

prev_

->

next_

=

elem

;

  

elem

->

next_

->

prev_

=

elem

;

  

elem

->

currentPtr_

 

=

 

currentPtr

;

 

}

private

:

 

template

<

int

 

n

,

typename

 

T

>

 

friend

 

class

 

DLList

;

 

DLListNode

<

n

>*

 

next_

;

 //nastepny element w liscie

 

DLListNode

<

n

>*

 

prev_

;

 //poprzedni element w liscie

 

DLListNode

<

n

>**

 

currentPtr_

;

//wskaznik na bieżąco iterowany element

}

;

Rysunek 7. 

Wzorzec Observer

Subject

+ Attach()
+ Detach()
+ Notify()

ConcreteSubject

+ SetState()
+ GetState()

- subjectState

ConcreteObserver

+ Update()

- observerState

<<interfejs>>

Observer

+ Update()

- observer

- subject

*

observerState=

subject->GetState

return

subjectState

for all o in observers

o->update()

background image

41

Ewolucja wzorca polimorfi zmu zewnętrznego w C++

www.sdjournal.org

Software Developer’s Journal   04/2007

Wymaga ono niejawnie, aby Klient był przypięty do Serwe-
ra zanim Serwer odpali Delegata. Z drugiej strony Klient mo-
że zwracać rezultat działania metody do Serwera pozwalając 
w ten sposób na przekazanie komunikatu w kierunku przeciw-
nym od Klienta do Serwera.

Taki scenariusz nie odbiega w dużym stopniu od zwykłego 

wywołania metody obiektu Klienta, więc będziemy go nazy-
wać w dalszej części Wywołaniem Delegata w odróżnieniu od 

Rysunek 9. 

Cykliczna lista dwukierunkowa

DLList

current

DLListNode

next

prev

currentPtr

DLListNode

next

prev

currentPtr

DLListNode

next

prev

currentPtr

DLListNode

next

prev

currentPtr

Rysunek 8. 

Scenariusz Rozgłoszenia.

Server

Klient A

request=onRequest

request()

onRequest

Klient B

onRequest

Klient C

onRequest

request=onRequest

request=onRequest

request=onRequest

request()

request()

for each

Listing 16. 

Cykliczna lista dwukierunkowa 

template

<

int

 

n

,

typename

 

  

T

=

DLListNode

<

n

>

 

>

  

class

 

DLList

 

:

 

public

 

  

DLListNode

<

n

>

  

{

  

public

:

 

 // poczatkowy element iteracji

  

void

 

start

()

 

const

 

{

current_

=

next_

;

}

 

 //nastepny element iteracji

  

void

 

next

()

 

const

  

{

current_

=

current_

->

next_

;

}

 

 

 //biezacy element iterowany

  

T

*

 

current

()

 

const

 

{

return

 

static_

  

cast

<

T

*>(

current_

==

this

?0 

:

 

current_

);

}

 

 

 //wstawienie elementu do listy

  

void

 

insert

(

DLListNode

<

n

>*

 

elem

)

  

{

  

DLListNode

<

n

>::

insert

(

elem

,

&

current_

);

 

}

 

 //usuniecie elementu z listy

  

void

 

remove

 

(

T

*

 

source

)

 

  

{

  

for

(

DLListNode

<

n

>*

 

x

=

next_

;

  

x

!=

this

;

 

x

=

x

->

next_

)

  

{

   

if

(

static_cast

<

T

*>(

x

)

->

isEqual

(*

source

))

   

{

    

delete

 

x

;

}

//wyczyszczenie zawartosci listy

   

void

 

clear

()

{

while

(

next_

!=

this

)

 

delete

 

next_

;

}

//w destruktorze wyczysc zawartosc

   

virtual

 ~

DLList

()

{

clear

();

}

private

:

   

mutable

 

DLListNode

<

n

>*

 

current_

;

}

;

background image

42

Inżynieria

oprogramowania

www.sdjournal.org

 Software Developer’s Journal   04/2007

scenariusza nazwanego Rozgłaszaniem, które realizuje po-
wiązanie Server-Klient typu jeden-do-wielu.

Rozgłaszanie to, inaczej mówiąc, wysyłanie komunika-

tów do wszystkich Klientów (w szczególności nie wysyłanie do 
żadnego), którzy zarejestrowali się na owe zdarzenie. W przy-
padku Rozgłoszenia nie mamy możliwości zwrócenia jedno-
znacznej wartości gdyż zależy ona od ilości zarejestrowanych 
klientów.

Problem Rozgłaszania jest dotknięty we wzorcu projekto-

wym Obserwator (ang. Observer). Wzorzec Obserwator znaj-
duje zastosowanie w sytuacji, gdy jeden obiekt powinien mieć 
możliwość powiadamiania innych obiektów, o których egzy-
stencji nie chce zakładać zbyt wiele, w tym nie interesuje go 
ich ilość. We wzorcu Obserwator można wyróżnić dwie podsta-
wowe hierarchie klas. Hierarchię obserwowaną i hierarchię ob-
serwującą. Hierarchia obserwowana ma korzeń w postaci kla-
sy 

Subject

, która to umożliwia dopinanie się dowolnej ilości ob-

serwatorów implementujących interfejs 

Observer

. Taka konkret-

na klasa obserwatora z kolei może w odpowiedzi na notyfi kację 

Rysunek 10. 

Asocjacje międzyobiektowe

DLList

current

DLListNode

next

prev

currentPtr

DLList

current

DLListNode

next

prev

currentPtr

DLList

current

DLListNode

next

prev

currentPtr

Association

DLListNode

next

prev

currentPtr

DLListNode

next

prev

currentPtr

Association

DLListNode

next

prev

currentPtr

DLListNode

next

prev

currentPtr

Association

DLListNode

next

prev

currentPtr

DLListNode

next

prev

currentPtr

Listing 17. 

Interfejs Delegacji Rozgłoszenia

template

<

typename

 

RetVal

,

  

typename

 

Arg1

typename

  

Arg2

,... 

>

  

class

 

IeventDelegate

  

:

 

public

 

DLListNode

<

0

>

 

  , 

public

 

Icallable

<

RetVal

,

  

Arg1

,

Arg2

,...

>

  

{

  

public

:

  

virtual

 

bool

 

isEqual

(

const

  

IeventDelegate

<

RetVal

,

Arg1

,

  

Arg2

,...

>&

 

h

)

 

const

 

=

 0

;

  

virtual

 

IeventDelegate

*

  

clone

()

 

const

 

=

 0

;

}

;

background image

Ewolucja wzorca polimorfi zmu zewnętrznego w C++

43

www.sdjournal.org

Software Developer’s Journal   04/2007

o zmianie ze strony klasy konkretnej klasy obserwowanej 

Con-

creteSubject

 zapytać o jej bieżący stan i na tej podstawie zak-

tualizować lokalne informacje na temat klasy obserwowanej.

Patrząc z boku na wzorzec Obserwator zauważamy duże 

podobieństwo w jego strukturze do wzorca Adapter, z tą jed-
nak różnicą, że o ile Adapter stowarzysza obserwatora i ob-
serwowanego w stosunku jedne-do-jednego, to Obserwator 
pozwala na skojarzenie jeden-do-wielu. Rozszerzenie kon-
cepcji Delegata w taki sposób, aby można było łączyć je w re-

lacje jeden-do-wielu dawałoby w rezultacie – jak się można 
spodziewać – wzorzec Rozgłoszenia. Niestety naiwna imple-
mentacja Rozgłoszenia jako kolekcji delegatów, najeżona jest 
pułapkami. Podczas iteracji po kolekcji Delegatów obserwato-
rzy w odpowiedzi na wywołanie mogą ową kolekcję zmodyfi -
kować,  mamy  więc  do  wyboru  –  albo  kosztem  wydajności  – 
przed samą iteracją skopiować kolekcję delegatów a iterację 
przeprowadzić na tej właśnie kopii, lub zbudować taką „spryt-
ną kolekcję”, która zagwarantuje sama swoją spójność w cza-
sie iteracji.

Listing 19. 

Delegat Rozgłoszenia

template

<

typename

 

RetVal

,

  

typename

 

Arg1

typename

 

Arg2

,...

>

  

class

 

EventDelegate

  

{

  

public

:

  

explicit

 

EventDelegate

  

(

IeventDelegate

<

RetVal

,

Arg1

,

  

Arg2

,

Arg3

,

Arg4

,

Arg5

>*

 

ptr

=

0

)

  

:

 

ptr_

(

ptr

)

{}

  

RetVal

 

operator

()(

Arg1

 

arg1

,

  

Arg2

 

arg2

)

 

const

  

{

  

return

 

ptr_

->

call

(

arg1

arg2

);

  

}

  

private

:

  

IeventDelegate

<

RetVal

,

Arg1

,

  

Arg2

,

Arg3

,

Arg4

,

Arg5

>*

 

ptr_

;

}

;

Listing 18. 

Implementacja Delegacji Rozgłoszenia

template

<

class

 

T

typename

 

RetVal

,

  

typename

 

Arg1

typename

 

Arg2

,... 

>

  

class

 

EventDelegateImpl

 

:

  

public

 

virtual

 

IeventDelegate

  

<

RetVal

Arg1

Arg2

Arg3

,

  

Arg4

Arg5

>

  

{

  

public

:

                         

  

typedef

 

typename

 

Icallable

  

<

RetVal

Arg1

Arg2

,...

>

  

MethodType

<

T

>::

V

 

methodType

;

  

EventDelegateImpl

  

(

T

*

 

obj

methodType

 

method

)

 

  

:

 

obj_

(

obj

)

 , 

method_

(

method

)

 

{}

  

RetVal

 

call

(

Arg1

 

arg1

Arg2

  

arg2

, ...

)

 

const

  

return

 

(

obj_

->*

method_

)

  

(

arg1

arg2

, ...

);

  

}

  

protected

:

  

methodType

 

method_

;

  

T

 

*

obj_

;

}

;

Listing 20. 

Rozgłoszenie

template

<

typename

 

RetVal

,

  

typename

 

Arg1

typename

 

Arg2

,...

>

  

class

 

Event

  

:

 

public

 

DLList

<

0, 

IeventDelegate

  

<

RetVal

Arg1

Arg2

, ...

>

 

>

  

{

  

private

:

  

template

<

typename

 

T

>

  

struct

 

Tsel

   

{}

;

  

template

 

<

typename

 

R

>

 

  

R

 

callMethod

(

TSel

<

R

>

,

  

Arg1

 

arg1

Arg2

 

arg2

,...

)

  

{

  

assert

(!

empty

());

  

return

 

elem

()->

call

(

arg1

arg2

);

  

}

  

template

 

<

typename

 

R

>

 

  

void

 

callMethod

(

TSel

<

void

>

  ,

Arg1

 

arg1

Arg2

 

arg2

,...

)

  

{

  

for

(

start

();;

 

next

())

  

{

   

if

(

current

()==

0

)

 

break

;

   

current

()->

call

(

arg1

arg2

);

  

}

  

}

  

public

:

  

void

 

operator

 

+=

 

(

EventDelegate

  

<

RetVal

Arg1

Arg2

,...

>

 

method

)

  

{

  

IEventDelegate

<

void

Arg1

Arg2

,...

>

  

*

 

clone

=

method

.

get

()->

clone

();

  

insert

(

clone

);

  

void

 

operator

 

-=

 

(

EventDelegate

  

<

RetVal

Arg1

Arg2

,...

>

 

method

)

  

{

  

remove

(

method

.

get

());

  

}

  

RetVal

 

operator

()(

Arg1

 

arg1

,

  

Arg2

 

arg2

,...

)

  

{

  

return

 

callMethod

<

RetVal

>

  

(

TSel

<

RetVal

>()

,

arg1

arg2

,...

);

 

}

 

}

;

background image

44

Inżynieria

oprogramowania

www.sdjournal.org

 Software Developer’s Journal   04/2007

Sprytna kolekcja ma swoje początki w implementacji 

sprytnego-wskaźnika opartego o cykliczną listę dwukierun-
kową [Alex01]. Elementy tej kolekcji w trakcie niszczenia – 
w czasie wywoływania destruktorów – potrafi ą się automa-
tycznie z listy wypiąć. Co więcej, taka „sprytna kolekcja” po-
trafi  również odpowiednio skorygować iterator przesuwa-
jąc go w razie potrzeby na odpowiednią prawidłową pozy-
cję. Kolekcja, na której się skupimy będzie bazowała na ta-
kim rozwiązaniu.

Przejdźmy do implementacji. Element takiej listy zadekla-

rujemy jako szablon. Nietypowy parametr tego szablonu przy-
da nam się później (Listing 15).

Sama lista dwukierunkowa. Przedstawia ją Listing 16.
Dysponując taką listą możemy przejść do implementa-

cji generycznego Obserwatora – czy inaczej mówiąc Rozgło-
szenia. Abstrakcyjny interfejs Delegata, który może się pod-
piąć do rozgłoszenia odziedziczymy po elemencie listy, a co 
za tym idzie będziemy mogli takie Delegaty dodawać do sa-
mej listy. Patrz Listing 17.

Implementacja z kolei jest identyczna z implementacją De-

legata. Przedstawia to Listing 18.

Podobnie jak klasa 

EventDelegate

, zarządzająca życiem 

delegata i wyposażająca go w operator wywołania funkcji (Li-
sting 19).

Przejdźmy teraz do klasy samego Rozgłoszenia odziedzi-

czonej po sprytnej kolekcji. Przedstawia to Listing 20.

Listing 21. 

Implementacja przykładu przy użyciu 

Rozgłoszeń

class

 

Button

  

{

  

public

:

  

Event

<

void

>

 

eventPush

;

 

 // function used by

  

Framework

 

to

 

generate

 

event

  

void

 

simulatePush

()

  

{

  

std

::

cout

 

<<

 

"

  PUSHING BUTTON"

 

<<

 

std

::

endl

;

  

eventPush

();

  

}

}

;

class

 

Mp3Player

 

{

  

public

:

  

virtual

 ~

Mp3Player

()

  

{

std

::

cout

 

<<

 

"MP3Player IS

  DEAD"

 

<<

 

std

::

endl

;

}

  

void

 

play

()

 

{

std

::

cout

 

<<

  

"PLAY"

 

<<

 

std

::

endl

;

}

  

void

 

stop

()

 

{

std

::

cout

 

<<

  

"STOP"

 

<<

 

std

::

endl

;

}

  

Event

<

void

,

int

>

 

eventProgress

;

  

void

 

simulateProgress

()

  

{

  

std

::

vector

<

int

>

 

positions

;

  

for

(

int

 

i

=

0

;

i

<=

100

;

i

+=

10

)

  

positions

.

push_back

(

i

);

  

std

::

for_each

(

positions

.

begin

()

,

positions

.

end

()

,

eventProgress

);

 

}

}

;

Listing 22. 

Konfi guracja rozgłoszeń w przykładzie

void

 

main

(

char

*

 

args

[]

,

int

 

argc

)

{

  

std

::

auto_ptr

<

Mp3Player

>

  

aMp3Player

(

new

 

Mp3Player

);

  

Button

 

playButton

;

  

Button

 

stopButton

;

  

ProgressBar

 

progressBar

;

  

playButton

.

eventPush

+=

delegate

  

cast

<

Event

>(

aMp3Player

.

get

()

,

  

&

Mp3Player

::

play

);

  

stopButton

.

eventPush

+=

delegate

  

cast

<

Event

>(

aMp3Player

.

get

()

,

  

&

Mp3Player

::

stop

);

  

aMp3Player

->

eventProgress

  

+=

delegate_cast

<

Event

>

  

(&

progressBar

,

&

ProgressBar

  

setProgress

);

 

 // done by framework

  

playButton

.

simulatePush

();

  

stopButton

.

simulatePush

();

  

aMp3Player

->

simulateProgress

();

  

playButton

.

eventPush

-=

delegate

  

cast

<

Event

>(

aMp3Player

.

get

()

,

  

&

Mp3Player

::

play

);

 

 // kill mp3 player

  

aMp3Player

.

reset

();

 

 // done by framework

  

playButton

.

simulatePush

();

 // fail

  

stopButton

.

simulatePush

();

}

Listing 23. 

Interfejs Delegacji Automatycznego 

Rozgłoszenia

template

<

typename

 

RetVal

,

typename

 

Arg1

typename

 

Arg2

,... 

>

class

 

IAutoEventDelegate

 

:

 

public

 

DLListNode

<

0

>

 , 

public

 

DLListNode

<

1

>

 , 

public

 

Icallable

<

RetVal

,

Arg1

,

Arg2

,...

>

{

public

:

 

virtual

 

bool

 

isEqual

(

const

IautoEventDelegate

<

RetVal

Arg1

,

Arg2

,...

>&

 

h

)

 

const

 

=

 0

;

 

virtual

 

IautoEventDelegate

*

 

clone

()

 

const

 

=

 0

;

 

virtual

 

void

 

connect

()

 

=

 0

;

}

;

background image

46

Inżynieria

oprogramowania

www.sdjournal.org

 Software Developer’s Journal   04/2007

Komentarza wymaga zastosowana tu sztuczka, zwana ty-

pe-selection. Pozwala ona na symulacje przeciążania szablo-
nów metod i szerzej jest opisana w pracy [Alex01]. Dzięki niej 
otrzymujemy specjalizację dla przypadku, gdy interesuje nas 
wartość zwracana i dla przypadku, gdy takiej wartości nie po-
trzebujemy.

Wróćmy do naszego przykładu z klasą 

Mp3Player

 i jej inter-

fejsem użytkownika.

Przy użyciu Rozgłaszania możemy ten problem zaimple-

mentować tak jak przedstawia to Listing 21.

A część konfi guracyjną przedstawia Listing 22.
Niestety ten przykład powoduje błąd czasu wykonywania. 

Mp3Player

 jest niszczony przed odpisaniem się od przycisku 

stop. Symulacja wciśnięcia tego przycisku powoduje odwoła-
nie się do nieistniejącego obiektu. Aby temu zapobiec musi-
my zautomatyzować proces odpinania się obserwatorów od 
obiektu obserwowanego. Jesteśmy już na to gotowi dysponu-
jąc powyższa implementacją listy dwukierunkowej.

Automatyczne wyrejestrowywanie 

się w czasie destrukcji obiektu

Każdy Delegat wiążący obserwatora z obiektem obserwo-
wanym jest swoistą relacją i powinien być związany z dwoma 
obiektami jednocześnie. W momencie śmierci jednego z nich 
cała relacja powinna być niszczona powodując automatyczne 
wyrejestrowanie się obserwatora z obiektu obserwowanego 
(Rysunek 10 i Listing 23).

Interfejs dziedziczy po dwóch typach elementów listy dwu-

kierunkowej, ponadto dodaje wirtualną metodę 

connect

, któ-

ra w implementacji wpina odpowiedni element do drugiej listy 
związanej z obiektem obserwatora (Listing 24).

Ostatnim etapem jest stworzenie klasy automatycznego 

generycznego Obserwatora. Patrz Listing 25.

Podmieniając w naszym przykładzie klasę 

Event

 na 

Auto-

Event

, nie doświadczamy już błędu czasu wykonania, gdyż 

Listing 24. 

Implementacja Delegacji Automatycznego 

Rozgłoszenia

class

 

AutoEventDelegateImpl

  

:

 

public

 

IautoEventDelegate

  

<

RetVal

,

Arg1

,

Arg2

,...

>

  

{

  

public

:

  

virtual

 

void

 

connect

()

  

{

  

obj

->

DLListNode

<

1

>

  

insert

(

this

,0

);

  

}

}

;

Listing 25. 

Przykład przy wykorzystaniu Automatycznych 

Rozgłoszeń

template

<

typename

 

RetVal

,

typename

 

Arg1

typename

 

Arg2

,...

>

class

 

AutoEvent

  

:

 

public

 

DLList

<

0, 

Iauto

EventDelegate

<

RetVal

Arg1

Arg2

, ...

>

 

>

{

public

:

 

void

 

operator

 

+=

 

(

Auto

EventDelegate

<

RetVal

,

Arg1

Arg2

,...

>

 

method

)

 

{

  

IautoEventDelegate

<

void

,

Arg1

Arg2

,...

>*

 

clone

=

method

.

get

()->

clone

();

  

clone

->

connect

();

  

insert

(

clone

);

 

}

 

void

 

operator

 

-=

 

(

AutoEvent

Delegate

<

RetVal

Arg1

,

Arg2

,...

>

 

method

)

 

{

  

remove

(

method

.

get

());

 

}

 

RetVal

 

operator

()(

Arg1

 

arg1

,

Arg2

 

arg2

,...

)

 

{

  

return

 

callMethod

<

RetVal

>

(

TSel

<

RetVal

>()

,

arg1

arg2

,...

);

 

}

}

;

Tabela 1. 

Porównanie funkcjonalne narzędzi wspierających wzorzec polimorfi zmu zewnętrznego

Cecha

Delegat

Event z wartością 
zwracaną

Event bez wartości zwracanej

Separacja interfejsów

tak

tak

Tak

Bezpieczeństwo w przypadku 
„śmierci” Klienta lub Serwera

nie

Tak

Tak

Relacja Klient-Serwer

1-1

1-1

1-N

Wartość zwracana

tak

tak

Nie

Koszt

a) virtual call
b) pointer to method
c) memory allocation 
for method binder

a) virtual call
b) pointer to method
c) association extraction
d) memory allocation for 
method binder
e) memory allocation for 
association

a) for-each virtual call
b) for-each pointer to method
c) for-each association extraction
d) for-each memory allocation for me-
thod binder 
e) for-each memory allocation per as-
sociation

background image

47

Ewolucja wzorca polimorfi zmu zewnętrznego w C++

www.sdjournal.org

Software Developer’s Journal   04/2007

klasa 

Mp3Player

 będąc niszczona automatycznie wyrejestruje 

wszystkich przypiętych do siebie obserwatorów w tym przy-
padku przycisk Stop.

Problemy w aplikacjach 

wielowątkowych

Przedstawione tu mechanizmy zakładały, że cała rzecz od-
bywa się w kontekście pojedynczego wątku. Powstaje pyta-
nie: jaki wpływ ma próba użycia owych mechanizmów w apli-
kacjach wielowątkowych? Aby odpowiedzieć sobie na to pyta-
nie musimy spojrzeć jeszcze raz na interfejs komponentu ja-
ko na twór składający się z części udostępnianej i wymaga-
nej. Część udostępniana komponentu może być zaprojekto-
wana z myślą o bezpiecznym dostępie z wielu wątków użyt-

kowników tego komponentu. Z kolei z drugiej strony kompo-
nent zgłaszający jakieś zgłoszenie może działać w jeszcze in-
nym wątku. W przypadku użycia Delegatów, czy Rozgłoszeń 
musimy o tym pamiętać.

Po pierwsze do Delegata lub Rozgłoszenie użytkownik 

może się przypiąć z innego wątku niż wątek w którym Dele-
gat lub Rozgłoszenie działa, ponadto Rozgłoszenie lub Dele-
gat może się odpalać się w kontekście wątku innego niż wą-
tek użytkownika komponentu. Musimy więc chronić w jakiś 
sposób zarówno proces przypinania/odpinania się od Dele-
gata czy Rozgłoszenia, jak również pilnować tego aby w trak-
cie odpalania Delegata lub Rozgłoszenia inne wątki zaczeka-
ły z przypinaniem/odpinaniem. Owe zabezpieczenie dość ła-
two można zrealizować przy wykorzystaniu wielowątkowe-
go wzorca projektowego o nazwie Monitor opisanego w pra-
cy [Schm00], do której odsyłam zainteresowanych. Pozwolę 
sobie tylko zasugerować rozwiązanie opierające się na mute-
xsie chroniącym cały interfejs udostępniany komponentu.

Podsumowanie

Artykuł ten miał na celu w główniej mierze pokazanie w ja-
ki sposób można w języku C++ uzyskać jednolity styl archi-
tektoniczny bazujący na koncepcji polimorfi zmu  zewnętrzne-
go. Separowalność interfejsów zmniejsza w znacznym stop-
niu zależności pomiędzy obiektami, a więc zmniejsza wiel-
kość szczepienia (ang. coupling) tak wytworzonej struktury, 
jednocześnie dając szansę na zwiększenie spójności logicz-
nej  (ang. cohesion) poszczególnych komponentów, co jest 
celem każdej dobrej architektury systemu informatycznego. 
Funktory, którymi są 

Delegate

 i 

Event

 nie są same w sobie re-

wolucją. Z problematyką polimorfi zmu zewnętrznego zderzy-
li się chociażby twórcy modelu COM w fi rmie Microsoft, chcąc 
ujednolicić zdarzeniowy interfejs komponentów. W najnow-
szej implementacji zarządzanego C++ są Delegaty są expli-
cite wymienione jako konstrukcje podstawowe [MSDN], dzia-
łają one jednak na innej zasadzie i są o wiele bardziej zasobo-
żerne. Przedstawione tu konstrukcje zainspirowane są praca-
mi [Clee97], [Clug05] oraz [Jaku97]. Koncepcja cyklicznej listy 
dwukierunkowej pochodzi natomiast z pracy Risto Lankinen 
opublikowanej na grupach dyskusyjnych w listopadzie 1995 
a opisanej szerzej w [Alex01]. Przedstawione tu konstrukcje 
zostały zweryfi kowane w rzeczywistych projektach komercyj-
nych cechując się dużą wydajnością i zapewniając znaczne 
zwiększenie jakości kodu programistycznego. 



Bibliografi a

•   [Abra04] David Abrahams, Aleksey Gurtovoy. C++ Template 

Metaprogramming. ISBN: 0321227255: Addison-Wesley, 2004;

•   [Alex01] Andrei Alexandrescu. Modern C++ Design: Ge-

neric Programming and Design Patterns Applied. ISBN: 
0201704315, Boston, MA: Addison-Wesley, 2001;

•   [Clee97] Cleeland, Chris, Douglas C. Schmidt, Tim Harrison. 

External Polymorphism”, PATTERN LANGUAGES OF PRO-
GRAM DESIGN, volume 3, edited by Robert Martin, Frank Bu-
schmann, and Dirke Riehle:Addison-Wesley, Reading MA, 1997.

•   http://www.cs.wustl.edu/~schmidt/PDF/External-Polymor-

phism.pdf;

•   [Clug05] Don Clugston. Member Function Pointers and the 

Fastest Possible C++ Delegates. http://www.codeproject.com/
cpp/FastDelegate.asp;

•   [Gamm95] E. Gamma, R. Helm, R. Johnson, and J. Vlissides. 

Design Patterns: Elements of Reusable Object-Oriented So-
ftware. Reading, MA: Addison-Wesley, 1995.

•   [Jaku97] Paul Jakubik. „Callback Implementations in C++” 
•   http://www.newty.de/jakubik/callback.pdf, 1997;
•   [Lipp96] Stanley B. Lippman. Inside the C++ Object Model. 

ISBN: 0201834545: Addison-Wesley, 1996;

•   [MSDN] Generic Delegates (C++) http://msdn2.microsoft.com/

en-us/library/213x8e7w.aspx;

•   [Schm00] Douglas Schmidt, Michael Stal, Hans Rohnert, 

Frank Buschmann. Pattern-Oriented Software Architecture, 
Volume 2, Patterns for Concurrent and Networked Objects 
ISBN: 9780471606956: Addison-Wesley, 2000.

R

E

K

L

A

M

A