2007 04 Ewolucja wzorca polimorfizmu zewnętrznego w C [Inzynieria Oprogramowania]

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

i

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

a

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_

!=

0

&&

*

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


Wyszukiwarka

Podobne podstrony:
2007 06 UML – potrzeba standaryzacji notacji [Inzynieria Oprogramowania]
2006 04 Rozszerzenie wzorca Template [Inzynieria Oprogramowania]
2007 05 Mechanizm koncepcji w języku C nowe oblicze szablonów [Inzynieria Oprogramowania]
2007 10 Extreme Programming (XP) i CMMI – Kreatywność, czy Dyscyplina [Inzynieria Oprogramowania]
2007 03 Inspekcje kodu jako skuteczna metoda weryfikacji oprogramowania [Inzynieria Oprogramowania]
2007 08 UML – modelowanie statycznych aspektów oprogramowania [Inzynieria Oprogramowania]
2007 07 Wykorzystanie przypadków użycia do modelowania zachowania [Inzynieria Oprogramowania]
2007 11 UML – modelowanie dynamicznych aspektów oprogramowania [Inzynieria Oprogramowania]
2007 11 Extreme Programming i CMMI [Inzynieria Oprogramowania]
2007 05 Mechanizm koncepcji w języku C nowe oblicze szablonów [Inzynieria Oprogramowania]
2007 07 Programowanie aplikacji wielowątkowych w języku C w oparciu o wzorce projektowe [Inzynieria

więcej podobnych podstron