Professional Documents
Culture Documents
Wzorce projektowe
Autor: Russ Olsen
Tumaczenie: Mikoaj Szczepaniak
ISBN: 978-83-246-1688-6
Tytu oryginau: Design Patterns in Ruby
(Addison-Wesley Professional Ruby Series)
Format: 172x245, stron: 370
Stworzony w 1995 roku przez Yukihiro Matsumoto jzyk Ruby dziki swym unikalnym
moliwociom zdobywa serca programistw na caym wiecie. Cechy, ktre podbijaj
to nieufne rodowisko, to midzy innymi prosta skadnia z wbudowanymi w ni
wyraeniami regularnymi, automatyczne oczyszczanie pamici i wiele, wiele innych.
Ogromna i chtna do pomocy spoeczno czyni to rozwizanie jeszcze bardziej
atrakcyjnym. Ruby pozwala na korzystanie ze wzorcw projektowych zbioru zasad
i regu prowadzcych do celu w najlepszy, najszybszy i najbardziej elastyczny sposb.
Wzorce projektowe kojarz si gwnie z jzykami Java oraz C i C++. Ksika Ruby.
Wzorce projektowe pokazuje, e mona ich z powodzeniem uywa rwnie w jzyku
Ruby. Dowiesz si z niej, w jaki sposb wykorzysta znane wzorce, takie jak Observer,
Singleton czy te Proxy. Autor przedstawi Ci rwnie nowe wzorce, ktre ze wzgldu
na cechy jzyka Ruby mog zosta w nim zastosowane. Jednak zanim przejdziesz
do ich omawiania, Russ poprowadzi Ci przez podstawy programowania w tym jzyku.
Nauczysz si uywa midzy innymi ptli, instrukcji warunkowych, wyrae regularnych.
Niewtpliwie Twoj ciekawo wzbudzi tak zwany duck typing, ktry oczywicie take
zosta dokadnie tu omwiony. Russ Olsen dziki swojemu wieloletniemu dowiadczeniu
kady wzorzec ilustruje przykadem z ycia wzitym. Uatwi Ci to przyswojenie
i zastosowanie we wasnych projektach przedstawionych tu wzorcw.
Wydawnictwo Helion
ul. Kociuszki 1c
44-100 Gliwice
tel. 032 230 98 63
e-mail: helion@helion.pl
S PIS TRECI
CZ I
Rozdzia 1.
Rozdzia 2.
CZ II
Rozdzia 3.
Urozmaicanie algorytmw
za pomoc wzorca projektowego Template Method ................................. 83
Jak stawi czoo typowym problemom .......................................................... 84
Izolowanie elementw zachowujcych dotychczasow form ................ 85
Odkrywanie wzorca projektowego Template Method .............................. 88
Metody zaczepienia .......................................................................................... 89
Gdzie si waciwie podziay wszystkie te deklaracje? ............................... 92
Typy, bezpiecze stwo i elastyczno ............................................................. 93
Testy jednostkowe nie maj charakteru opcjonalnego ............................... 95
Uywanie i naduywanie wzorca projektowego Template Method ....... 97
Szablony w praktycznych zastosowaniach ................................................... 98
Podsumowanie .................................................................................................. 99
Rozdzia 4.
Rozdzia 5.
SPIS TRECI
Rozdzia 7.
Rozdzia 8.
Doprowadzanie spraw do ko
ca za pomoc wzorca Command ........... 163
Eksplozja podklas ............................................................................................ 164
Prostsze rozwizanie ...................................................................................... 165
Stosowanie blokw kodu w roli polece ..................................................... 166
Rejestrowanie polece .................................................................................... 167
Wycofywanie operacji za pomoc wzorca Command .............................. 170
Kolejkowanie polece .................................................................................... 173
Uywanie i naduywanie wzorca projektowego Command .................. 174
Wzorzec projektowy Command w praktycznych zastosowaniach ........ 175
Migracje w ramach interfejsu ActiveRecord ................................... 175
Madeleine ............................................................................................. 176
Podsumowanie ................................................................................................ 179
Rozdzia 9.
10
Rozdzia 10.
Rozdzia 11.
Rozdzia 12.
SPIS TRECI
11
Rozdzia 14.
Rozdzia 15.
12
CZ III
Rozdzia 16.
Rozdzia 17.
Rozdzia 18.
SPIS TRECI
13
Dodatek A
Dodatek B
DODATKI ................................................................................345
R OZDZIA 4.
Zastpowanie
algorytmu strategi
W poprzednim rozdziale szukalimy odpowiedzi na pytanie: Jak zrnicowa wybran
cz algorytmu? Jak sprawi, by trzeci krok procesu piciokrokowego raz podejmowa
jedne dziaania, a innym razem realizowa nieco inne zadania? W rozdziale 3. przyjlimy rozwizanie polegajce na stosowaniu wzorca projektowego Template Method,
czyli na tworzeniu klasy bazowej z metod szablonow odpowiedzialn za oglne
przetwarzanie podklas charakterystycznych dla mechanizmw szczegowych. Gdybymy raz chcieli realizowa jeden wariant, by innym razem stosowa mechanizm
alternatywny, powinnimy opracowa dwie podklasy, po jednej dla obu scenariuszy.
Wzorzec projektowy Template Method ma, niestety, pewne wady, z ktrych najpowaniejsz jest konieczno korzystania z relacji dziedziczenia (wanie wok niej
opracowano ten wzorzec). Jak wiemy z rozdziau 1., stosowanie cisej relacji dziedziczenia niesie ze sob wiele negatywnych konsekwencji. Niezalenie od wysiku,
jaki woymy w rozwane projektowanie naszego kodu, podklasy bd cile zwizane ze swoj nadklas, co w przypadku tej relacji jest zupenie naturalne. Techniki
opracowane na podstawie relacji dziedziczenia, w tym wzorzec Template Method,
ograniczaj wic elastyczno naszego kodu. Po wybraniu okrelonej wersji algorytmu (w naszym przypadku takim krokiem byo utworzenie obiektu klasy HTMLReport), zmiana tego trybu na inny moe si okaza niezwykle trudna. Gdybymy
zastosowali wzorzec projektowy Template Method i zdecydowali si na zmian
formatu raportu, musielibymy utworzy nowy obiekt raportu, np. obiekt klasy
PlainTextReport, tylko po to, by uy nowego formatu. Mamy inne wyjcie?
102
Klas odpowiedzialn za formatowanie raportu w wersji tekstowej mona by zaimplementowa w nastpujcy sposb:
class PlainTextFormatter < Formatter
def output_report(title, text)
puts("***** #{title} *****")
text.each do |line|
puts(line)
end
end
end
Udao nam si przenie szczegowy kod zwizany z formatowaniem danych wynikowych poza klas Report, zatem jej definicja bdzie teraz nieporwnanie prostsza:
class Report
attr_reader :title, :text
attr_accessor :formatter
103
def initialize(formatter)
@title = 'Raport miesiczny'
@text = [ 'Wszystko idzie', 'naprawd dobrze.' ]
@formatter = formatter
end
def output_report
@formatter.output_report( @title, @text )
end
end
Okazuje si, e wprowadzona zmiana tylko nieznacznie komplikuje proces korzystania z klasy Report w nowym ksztacie. Musimy teraz przekazywa na wejciu jej konstruktora waciwy obiekt formatujcy:
report = Report.new(HTMLFormatter.new)
report.output_report
RYSUNEK 4.1.
Wzorzec projektowy
Strategy
Z drugiej strony wybr strategii ma znaczenie ze wzgldu na rnice w sposobie realizacji interesujcych nas zada . W naszym przykadzie jedna ze strategii formatowania generuje raport w jzyku HTML, druga generuje raport w formie zwykego
tekstu. Gdybymy zastosowali wzorzec Strategy w systemie obliczajcym wysoko
podatku dochodowego, moglibymy uy jednej strategii do wyznaczania podatku
paconego przez etatowego pracownika i innej do obliczania podatku uiszczanego
przez pracownika zatrudnionego na podstawie umowy o dzieo.
104
Wzorzec projektowy Strategy ma wiele praktycznych zalet. Przykad raportw pokazuje, e stosujc ten wzorzec, moemy zapewni skuteczniejsz izolacj naszego
kodu dziki wyodrbnieniu zbioru strategii poza klas gwn. Wzorzec Strategy
zwalnia klas Report z jakiejkolwiek odpowiedzialnoci czy choby koniecznoci
skadowania informacji o formacie generowanych raportw.
Co wicej, poniewa wzorzec Strategy opiera si na relacji kompozycji i technice delegacji (zamiast na dziedziczeniu), zmiana strategii w czasie wykonywania programu
nie stanowi adnego problemu. Wystarczy wymieni obiekt strategii:
report = Report.new(HTMLFormatter.new)
report.output_report
report.formatter = PlainTextFormatter.new
report.output_report
105
Chocia opisana technika przekazywania kontekstu do strategii skutecznie upraszcza przepyw danych, warto mie na uwadze to, e stosujc wskazane rozwizanie,
zacieniamy zwizki czce kontekst i strategi. W ten sposb istotnie zwikszamy
ryzyko wzajemnego uzalenienia klasy kontekstu i klas strategii.
106
107
class PlainTextFormatter
def output_report(context)
puts("***** #{context.title} *****")
context.text.each do |line|
puts(line)
end
end
end
Jeli porwnamy ten kod z poprzedni wersj, przekonamy si, e rezygnacja z klasy
bazowej Formatter nie wymagaa zbyt wielu dodatkowych zmian. Dziki dynamicznej
kontroli typw nadal moemy generowa prawidowe raporty. Chocia obie wersje
dziaaj zgodnie z zaoeniami, do wiata Ruby duo lepiej pasuje model pozbawiony
klasy bazowej Formatter.
Stosuje si te terminy domknicia (ang. closure) oraz lambdy (std nazwa naszej metody tworzcej
obiekt Proc).
108
kontener obejmujcy cay kod zdefiniowany pomidzy sowami do i end. W przedstawionym przykadzie obiekt Proc jest wskazywany przez zmienn hello. Kod
reprezentowany przez obiekt Proc moemy wykona, wywoujc metod call (trudno
sobie wyobrazi lepsz nazw). Jeli w przedstawionym przykadzie wywoamy metod
call obiektu Proc:
hello.call
otrzymamy komunikaty:
Witaj
Jestem wewntrz obiektu Proc.
Wyjtkowo cennym aspektem obiektw Proc jest zdolno do funkcjonowania w otaczajcym je rodowisku. Oznacza to, e wszelkie zmienne widoczne w momencie
tworzenia obiektu Proc pozostaj dla tego obiektu dostpne take w czasie wykonywania programu. Na przykad w poniszym fragmencie kodu istnieje tylko jedna zmienna name:
name = 'Jan'
proc = Proc.new do
name = 'Maria'
end
proc.call
puts(name)
Kiedy wykonamy ten kod, zmiennej name w pierwszej kolejnoci zostanie przypisany
a cuch "Jan", po czym (po wywoaniu obiektu Proc) warto tej zmiennej zostanie
zastpiona przez a cuch "Maria".
Oznacza to, e ostatecznie Ruby wywietli a cuch "Maria". Z myl o programistach,
ktrym konstrukcja do-end wydaje si zbyt rozbudowana, Ruby oferuje krtsz skadni zoon tylko z nawiasw klamrowych. Poniej przedstawiono przykad uycia
tej skadni do utworzenia naszego obiektu Proc:
hello = lambda {
puts('Witaj, jestem wewntrz obiektu Proc')
}
Uczciwo nakazuje wspomnie o istotnej rnicy dzielcej obie konstrukcje. W wyraeniach jzyka
Ruby nawiasy klamrowe maj wyszy priorytet od pary sw kluczowych do i end. Krtko mwic,
nawiasy klamrowe s cilej zwizane z wyraeniami. Specyfika nawiasw klamrowych jest widoczna dopiero wtedy, gdy zrezygnujemy z opcjonalnych nawiasw okrgych.
109
Obiekty Proc pod wieloma wzgldami przypominaj metody. Podobnie jak metody,
obiekty Proc nie tylko reprezentuj fragmenty kodu, ale te mog zwraca wartoci.
Obiekt Proc zawsze zwraca ostatni warto wyznaczon w danym bloku aby
zwrci warto z poziomu takiego obiektu, wystarczy si upewni, e jest ona wyznaczana przez jego ostatnie wyraenie. Wszelkie wartoci zwracane przez obiekty
Proc s przekazywane do kodu wywoujcego za porednictwem metody call.
Oznacza to, e kod w postaci:
return_24 = lambda {24}
puts(return_24.call)
wywietli warto:
24
Dla obiektw Proc mona te definiowa parametry, cho skadnia tego rodzaju definicji jest do nietypowa. Zamiast otacza list parametrw tradycyjnymi nawiasami
okrgymi, naley je umieszcza pomidzy dwoma symbolami |:
multiply = lambda {|x, y| x * y}
Kod w tej formie definiuje obiekt Proc otrzymujcy dwa parametry, wyznaczajcy ich
iloczyn i zwracajcy otrzyman warto. Aby wywoa obiekt Proc z parametrami,
wystarczy je przekaza na wejciu metody call:
n = multiply.call(20, 3)
puts(n)
n = multiply.call(10, 50)
puts(n)
Moliwo przekazywania blokw kodu jest na tyle przydatna, e twrcy Ruby zdecydowali si zdefiniowa z myl o tej technice specjaln, skrcon konstrukcj skadniow. Jeli chcemy przekaza blok kodu na wejciu metody, wystarczy go doczy
do jej wywoania. Tak wywoana metoda moe nastpnie wykona ten blok kodu
za pomoc sowa kluczowego yield. Poniej zdefiniowano przykadow metod
wywietlajc komunikat, wykonujc otrzymany blok kodu i wywietlajc kolejny
komunikat:
def run_it
puts("Przed wykonaniem yield")
yield
puts("Po wykonaniu yield")
end
A tak moe wyglda wywoanie metody run_it. Warto pamita, e w ten sposb
dopisujemy blok kodu na koniec wywoania naszej metody:
110
run_it do
puts('Witaj')
puts('Przybywam do Ciebie z wntrza bloku kodu')
end
Jeli przekazany blok kodu sam otrzymuje jakie parametry, naley je przekaza za
porednictwem sowa kluczowego yield. Oznacza to, e poniszy kod:
def run_it_with_parameter
puts("Przed wykonaniem yield")
yield(24)
puts("Po wykonaniu yield")
end
run_it_with_parameter do |x|
puts('Witaj z wntrza bloku kodu')
puts("Parametr x ma warto #{x}")
end
111
KRTKA ANALIZA
KILKU PROSTYCH STRATEGII
Co bloki kodu i obiekty Proc maj wsplnego ze wzorcem projektowym Strategy?
Najkrcej mwic, strategi mona postrzega jako blok wykonywalnego kodu,
ktry wie, jak zrealizowa okrelone zadanie (np. formatowanie tekstu) i ktry
opakowano w formie obiektu. Przytoczona definicja brzmi znajomo obiekt Proc
bywa okrelany wanie jako fragment kodu opakowany w formie obiektu.
Wrmy teraz do naszego przykadowego systemu formatujcego raporty, gdzie zastosowanie strategii w formie obiektu Proc okazuje si wyjtkowo proste. Zmiany,
ktre musimy wprowadzi w kodzie klasy Report, ograniczaj si do poprzedzenia
parametru przekazywanego na wejciu metody initialize symbolem & i zmiany
nazwy wywoywanej metody z output_report na call:
class Report
attr_reader :title, :text
attr_accessor :formatter
def initialize(&formatter)
@title = 'Raport miesiczny'
@text = [ 'Wszystko idzie', 'naprawd dobrze.' ]
@formatter = formatter
end
def output_report
@formatter.call( self )
end
end
Z nieco inn sytuacj mamy jednak do czynienia w przypadku klas odpowiedzialnych za waciwe formatowanie. Musimy teraz stworzy obiekty Proc zamiast egzemplarzy naszych wyspecjalizowanych klas formatujcych:
HTML_FORMATTER = lambda do |context|
puts('<html>')
puts(' <head>')
puts(" <title>#{context.title}</title>")
puts(' </head>')
puts(' <body>')
context.text.each do |line|
112
puts(" <p>#{line}</p>" )
end
puts(' </body>')
puts
Skoro dysponujemy ju kodem formatujcym w formie obiektu Proc, moemy przystpi do tworzenia raportw. Poniewa dysponujemy obiektem Proc, a konstruktor
klasy Report oczekuje bloku kodu, tworzc nowy obiekt tej klasy musimy poprzedzi wspomniany obiekt Proc znakiem &:
report = Report.new &HTML_FORMATTER
report.output_report
UYWANIE I NADUYWANIE
WZORCA PROJEKTOWEGO STRATEGY
Najczstsz przyczyn bdnego implementowania wzorca projektowego Strategy
jest niewaciwe definiowanie interfejsu pomidzy kontekstem a strategiami. Musimy
pamita, e naszym celem jest wyodrbnienie kompletnego, spjnego i mniej lub
113
WZORZEC STRATEGY
W PRAKTYCZNYCH ZASTOSOWANIACH
Narzdzie rdoc (doczane do dystrybucji jzyka Ruby) zawiera wiele mechanizmw
opracowanych na podstawie klasycznego, zaproponowanego przez Band Czworga
i opartego na klasach wzorca projektowego Strategy. Zadaniem tego narzdzia jest
generowanie dokumentacji na podstawie kodu rdowego. rdoc oferuje moliwo
dokumentowania zarwno programw napisanych w Ruby, jak i programw opracowanych w C i (ratunku!) programw stworzonych w jzyku FORTRAN. Narzdzie
rdoc wykorzystuje wzorzec Strategy do obsugi poszczeglnych jzykw programowania kady analizator skadniowy (waciwy dla jzykw C, Ruby i FORTRAN)
ma posta strategii stworzonej z myl o innych danych wejciowych.
Narzdzie rdoc oferuje te uytkownikowi wybr formatu wyjciowego moemy
wybra jedn z wielu odmian jzyka HTML, jzyk XML bd jeden z formatw wykorzystywanych przez polecenie ri jzyka Ruby. Jak nietrudno odgadn, kady z tych
formatw wyjciowych take jest obsugiwany przez wyspecjalizowan strategi.
Relacje czce poszczeglne klasy strategii programu rdoc dobrze ilustruj oglne
podejcie do problemu dziedziczenia stosowane w jzyku Ruby. Zwizki klas reprezentujcych poszczeglne strategie przedstawiono na rysunku 4.2.
RYSUNEK 4.2.
Klasy generujce
narzdzia rdoc
Jak wida na rysunku 4.2, istniej cztery powizane ze sob strategie formatowania
danych wyjciowych (okrelanych w programie rdoc mianem generatorw) i jedna
strategia autonomiczna. Wszystkie cztery powizane strategie generuj dokumentacj
w podobnym formacie znane wszystkim konstrukcje <cotam> otoczone nawiasami
114
Metoda sort wywouje nasz blok kodu za kadym razem, gdy bdzie zmuszona porwna dwa elementy sortowanej tablicy. Nasz blok powinien zwraca warto 1, jeli
pierwszy element jest wikszy od drugiego; warto 0, jeli oba elementy s rwne,
i warto 1, jeli wikszy jest drugi element. Nieprzypadkowo dziaanie tego kodu
przypomina zachowanie operatora <=>.
PODSUMOWANIE
Wzorzec projektowy Strategy reprezentuje nieco inn, opart na delegowaniu zada
propozycj rozwizywania tego samego problemu, ktry jest rwnie rozwizywany
przez wzorzec Template Method. Zamiast upychania zmiennych czci algorytmu
w podklasach, implementujemy kad z wersji tego algorytmu w odrbnym obiekcie.
Moemy nastpnie urozmaica dziaania tego algorytmu przez wskazywanie obiektowi
kontekstu rnych obiektw strategii. Oznacza to, e o ile jedna strategia na przykad
generuje raport w formacie HTML, o tyle inna moe generowa ten sam raport
3
Format CHM jest odmian HTML-a wykorzystywan do generowania plikw pomocy firmy Microsoft.
Chciabym te podkreli, e to kod narzdzia rdoc nie ja sugeruje, e XML jest odmian jzyka HTML.
115
w formacie PDF; podobnie, jedna strategia moe wyznacza wysoko podatku odprowadzanego przez pracownika etatowego, by inna wyliczaa podatek naleny od
pracownika zatrudnionego na podstawie umowy o dzieo.
Mamy do dyspozycji kilka rozwiza dotyczcych przekazywania niezbdnych danych pomidzy obiektem kontekstu a obiektem strategii. Moemy albo przekazywa
wszystkie dane w formie parametrw wywoywanych metod obiektu strategii, albo
po prostu przekazywa obiektowi strategii referencj do caego obiektu kontekstu.
Bloki kodu jzyka Ruby, ktre w istocie maj posta kodu opakowywanego w ramach
tworzonego na bieco obiektu (a konkretnie obiektu Proc), wprost doskonale nadaj si do byskawicznego konstruowania prostych obiektw strategii.
Jak si niedugo przekonamy, wzorzec projektowy Strategy przypomina (przynajmniej na pierwszy rzut oka) wiele innych wzorcw. Wzorzec Strategy wymusza stosowanie obiektu (nazywanego kontekstem), ktry odpowiada za realizacj okrelonego
zadania. Okazuje si jednak, e wykonanie tego zadania wymaga od kontekstu
skorzystania z pomocy innego obiektu (okrelanego mianem strategii). Z bardzo
podobnym modelem mamy do czynienia w przypadku wzorca projektowego Observer (obserwatora), gdzie obiekt realizujcy pewne zadania kieruje wywoania do
drugiego obiektu, bez ktrego jego funkcjonowanie byoby niemoliwe.
Okazuje si, e jedyn rnic dzielc oba te wzorce jest intencja programisty. Celem
wzorca projektowego Strategy jest dostarczenie obiektowi kontekstu innego obiektu,
ktry wie, jak wykona okrelon wersj algorytmu. Zupenie inny cel przywieca
programicie stosujcemu wzorzec Observer ot stosujemy go wtedy, gdy
Chyba powinnimy te rozwaania przenie do innego rozdziau (tak si skada, e
bdzie to kolejny rozdzia).