Tytuł oryginału: JavaScript Patterns
Tłumaczenie: Rafał Jońca
ISBN: 978-83-246-3821-5
© Helion S.A. 2012.
Authorized Polish translation of the English edition of JavaScript Patterns ISBN 9780596806750 © 2010,
Yahoo!, Inc. All rights reserved.
This translation is published and sold by permission of O’Reilly Media, Inc., the owner of all rights to
publish and sell the same.
All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means,
electronic or mechanical, including photocopying, recording or by any information storage retrieval system,
without permission from the Publisher.
Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej
publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną,
fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje
naruszenie praw autorskich niniejszej publikacji.
Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich
właścicieli.
Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były
kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane
z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie
ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji
zawartych w książce.
Wydawnictwo HELION
ul. Kościuszki 1c, 44-100 GLIWICE
tel. 32 231 22 19, 32 230 98 63
e-mail: helion@helion.pl
WWW: http://helion.pl (księgarnia internetowa, katalog książek)
Drogi Czytelniku!
Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres
http://helion.pl/user/opinie/jascwz
Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję.
Printed in Poland.
5
Spis treci
Wstp
............................................................................................................................11
1. Wprowadzenie
............................................................................................................ 15
Wzorce
15
JavaScript — podstawowe cechy
16
Zorientowany obiektowo
16
Brak klas
17
Prototypy 18
rodowisko 18
ECMAScript 5
18
Narzdzie JSLint
19
Konsola
20
2. Podstawy
..................................................................................................................... 21
Tworzenie kodu atwego w konserwacji
21
Minimalizacja liczby zmiennych globalnych
22
Problem ze zmiennymi globalnymi
22
Efekty uboczne pominicia var
24
Dostp do obiektu globalnego
25
Wzorzec pojedynczego var
25
Przenoszenie deklaracji — problem rozrzuconych deklaracji var
26
Ptle for
27
Ptle for-in
29
Modyfikacja wbudowanych prototypów
31
Wzorzec konstrukcji switch
31
Unikanie niejawnego rzutowania
32
Unikanie eval()
32
Konwertowanie liczb funkcj parseInt()
34
Kup książkę
Poleć książkę
6
_ Spis
treci
Konwencje dotyczce kodu
34
Wcicia
35
Nawiasy klamrowe
35
Pooenie nawiasu otwierajcego
36
Biae spacje
37
Konwencje nazewnictwa
38
Konstruktory pisane od wielkiej litery
38
Oddzielanie wyrazów
39
Inne wzorce nazewnictwa
39
Pisanie komentarzy
40
Pisanie dokumentacji interfejsów programistycznych
41
Przykad dokumentacji YUIDoc
42
Pisanie w sposób uatwiajcy czytanie
44
Ocenianie kodu przez innych czonków zespou
45
Minifikowanie kodu tylko w systemie produkcyjnym
46
Uruchamiaj narzdzie JSLint
47
Podsumowanie 47
3. Literay
i konstruktory .................................................................................................49
Litera obiektu
49
Skadnia literau obiektowego
50
Obiekty z konstruktora
51
Puapka konstruktora Object
51
Wasne funkcje konstruujce
52
Warto zwracana przez konstruktor
53
Wzorce wymuszania uycia new
54
Konwencja nazewnictwa
54
Uycie that
54
Samowywoujcy si konstruktor
55
Litera tablicy
56
Skadnia literau tablicy
56
Puapka konstruktora Array
56
Sprawdzanie, czy obiekt jest tablic
57
JSON
58
Korzystanie z formatu JSON
58
Litera wyraenia regularnego
59
Skadnia literaowego wyraenia regularnego
60
Otoczki typów prostych
61
Obiekty bdów
62
Podsumowanie 63
Kup książkę
Poleć książkę
Spis treci
_
7
4. Funkcje
.........................................................................................................................65
Informacje ogólne
65
Stosowana terminologia
66
Deklaracje kontra wyraenia — nazwy i przenoszenie na pocztek
67
Waciwo name funkcji
68
Przenoszenie deklaracji funkcji
68
Wzorzec wywoania zwrotnego
70
Przykad wywoania zwrotnego
70
Wywoania zwrotne a zakres zmiennych
72
Funkcje obsugi zdarze asynchronicznych
73
Funkcje czasowe
73
Wywoania zwrotne w bibliotekach
74
Zwracanie funkcji
74
Samodefiniujce si funkcje
75
Funkcje natychmiastowe
76
Parametry funkcji natychmiastowych
77
Wartoci zwracane przez funkcje natychmiastowe
77
Zalety i zastosowanie
79
Natychmiastowa inicjalizacja obiektu
79
Usuwanie warunkowych wersji kodu
80
Waciwoci funkcji — wzorzec zapamitywania
82
Obiekty konfiguracyjne
83
Rozwijanie funkcji
84
Aplikacja funkcji
84
Aplikacja czciowa
85
Rozwijanie funkcji
87
Kiedy uywa aplikacji czciowej
89
Podsumowanie 89
5. Wzorce
tworzenia obiektów ...................................................................................... 91
Wzorzec przestrzeni nazw
91
Funkcja przestrzeni nazw ogólnego stosowania
92
Deklarowanie zalenoci
94
Metody i waciwoci prywatne
95
Skadowe prywatne
96
Metody uprzywilejowane
96
Problemy z prywatnoci
96
Literay obiektów a prywatno
98
Prototypy a prywatno
98
Udostpnianie funkcji prywatnych jako metod publicznych
99
Kup książkę
Poleć książkę
8
_ Spis
treci
Wzorzec moduu
100
Odkrywczy wzorzec moduu
102
Moduy, które tworz konstruktory
102
Import zmiennych globalnych do moduu
103
Wzorzec piaskownicy
103
Globalny konstruktor
104
Dodawanie moduów
105
Implementacja konstruktora
106
Skadowe statyczne
107
Publiczne skadowe statyczne
107
Prywatne skadowe statyczne
109
Stae obiektów
110
Wzorzec acucha wywoa
112
Wady i zalety wzorca acucha wywoa
112
Metoda method()
113
Podsumowanie 114
6. Wzorce
wielokrotnego uycia kodu ..........................................................................115
Klasyczne i nowoczesne wzorce dziedziczenia
115
Oczekiwane wyniki w przypadku stosowania wzorca klasycznego
116
Pierwszy wzorzec klasyczny — wzorzec domylny
117
Podanie wzdu acucha prototypów
117
Wady wzorca numer jeden
119
Drugi wzorzec klasyczny — poyczanie konstruktora
119
acuch prototypów
120
Dziedziczenie wielobazowe przy uyciu poyczania konstruktorów
121
Zalety i wady wzorca poyczania konstruktora
122
Trzeci wzorzec klasyczny — poyczanie i ustawianie prototypu
122
Czwarty wzorzec klasyczny — wspódzielenie prototypu
123
Pity wzorzec klasyczny — konstruktor tymczasowy
124
Zapamitywanie klasy nadrzdnej
125
Czyszczenie referencji na konstruktor
125
Podejcie klasowe
126
Dziedziczenie prototypowe
129
Dyskusja 129
Dodatki do standardu ECMAScript 5
130
Dziedziczenie przez kopiowanie waciwoci
131
Wzorzec wmieszania
132
Poyczanie metod
133
Przykad — poyczenie metody od obiektu Array
134
Poyczenie i przypisanie
134
Metoda Function.prototype.bind()
135
Podsumowanie 136
Kup książkę
Poleć książkę
Spis treci
_
9
7. Wzorce
projektowe ....................................................................................................137
Singleton
137
Uycie sowa kluczowego new
138
Instancja we waciwoci statycznej
139
Instancja w domkniciu
139
Fabryka
141
Wbudowane fabryki obiektów
143
Iterator
143
Dekorator
145
Sposób uycia
145
Implementacja 146
Implementacja wykorzystujca list
148
Strategia
149
Przykad walidacji danych
150
Fasada
152
Porednik
153
Przykad 153
Porednik jako pami podrczna
159
Mediator
160
Przykad mediatora
160
Obserwator 163
Pierwszy przykad — subskrypcja magazynu
163
Drugi przykad — gra w naciskanie klawiszy
166
Podsumowanie 169
8. DOM i wzorce dotyczce przegldarek ..................................................................... 171
Podzia zada
171
Skrypty wykorzystujce DOM
172
Dostp do DOM
173
Modyfikacja DOM
174
Zdarzenia
175
Obsuga zdarze
175
Delegacja zdarze
177
Dugo dziaajce skrypty
178
Funkcja setTimeout()
178
Skrypty obliczeniowe
179
Komunikacja z serwerem
179
Obiekt XMLHttpRequest
180
JSONP
181
Ramki i wywoania jako obrazy
184
Kup książkę
Poleć książkę
10
_ Spis
treci
Serwowanie kodu JavaScript klientom
184
czenie skryptów
184
Minifikacja i kompresja
185
Nagówek Expires
185
Wykorzystanie CDN
186
Strategie wczytywania skryptów
186
Lokalizacja elementu <script>
187
Wysyanie pliku HTML fragmentami
188
Dynamiczne elementy <script> zapewniajce nieblokujce pobieranie
189
Wczytywanie leniwe
190
Wczytywanie na danie
191
Wstpne wczytywanie kodu JavaScript
192
Podsumowanie 194
Skorowidz
.................................................................................................................. 195
Kup książkę
Poleć książkę
137
ROZDZIA 7.
Wzorce projektowe
Wzorce projektowe opisane w ksice tak zwanego gangu czworga oferuj rozwizania ty-
powych problemów zwizanych z projektowaniem oprogramowania zorientowanego obiektowo.
S dostpne ju od jakiego czasu i sprawdziy si w wielu rónych sytuacjach, warto wic
si z nimi zapozna i powici im nieco czasu.
Cho same te wzorce projektowe nie s uzalenione od jzyka programowania i implementa-
cji, byy analizowane przez wiele lat gównie z perspektywy jzyków o silnym sprawdzaniu
typów i statycznych (niezmiennych) klasach takich jak Java lub C++.
JavaScript jest jzykiem o lunej kontroli typów i bazuje na prototypach (a nie klasach), wic nie-
które z tych wzorców okazuj si wyjtkowo proste, a czasem wrcz banalne w implementacji.
Zacznijmy od przykadu sytuacji, w której w jzyku JavaScript rozwizanie wyglda inaczej
ni w przypadku jzyków statycznych bazujcych na klasach, czyli od wzorca singletonu.
Singleton
Wzorzec singletonu ma w zaoeniu zapewni tylko jedn instancj danej klasy. Oznacza to,
e próba utworzenia obiektu danej klasy po raz drugi powinna zwróci dokadnie ten sam
obiekt, który zosta zwrócony za pierwszym razem.
Jak zastosowa ten wzorzec w jzyku JavaScript? Nie mamy przecie klas, a jedynie obiekty.
Gdy powstaje nowy obiekt, nie ma w zasadzie drugiego identycznego, wic jest on automa-
tycznie singletonem. Utworzenie prostego obiektu za pomoc literau to doskonay przykad
utworzenia singletonu.
var obj = {
myprop: 'warto'
};
W JavaScripcie obiekty nie s sobie równe, jeli nie s dokadnie tym samym obiektem,
wic nawet jeli utworzy si dwa identyczne obiekty z takimi samymi wartociami, nie
bd równowane.
var obj2 = {
myprop: 'warto'
};
obj === obj2; // false
obj == obj2; // false
Kup książkę
Poleć książkę
138
_
Rozdzia 7. Wzorce projektowe
Mona wic stwierdzi, e za kadym razem, gdy powstaje nowy obiekt tworzony za pomoc
literau, powstaje nowy singleton, i to bez uycia dodatkowej skadni.
Czasem gdy ludzie mówi „singleton” w kontekcie jzyka JavaScript, maj na myli
wzorzec moduu opisany w rozdziale 5.
Uycie sowa kluczowego new
JavaScript jest jzykiem niestosujcym klas, wic dosowna definicja singletonu nie ma tu za-
stosowania. Z drugiej strony jzyk posiada sowo kluczowe
new
, które tworzy obiekty na
podstawie funkcji konstruujcych. Czasem tworzenie ich w ten sposób jako singletonów mo-
e by ciekawym podejciem. Ogólny pomys jest nastpujcy: kilkukrotne wywoanie funkcji
konstruujcej z uyciem
new
powinno spowodowa kadorazowo zwrócenie dokadnie tego
samego obiektu.
Przedstawiony poniej opis nie jest uyteczny w praktyce. Stanowi raczej teoretyczne
wyjanienie powodów powstania wzorca w jzykach statycznych o cisej kontroli
typów, w których to funkcje nie s penoprawnymi obiektami.
Poniszy przykad ilustruje oczekiwane zachowanie (pod warunkiem e nie wierzy si
w wiaty równolege i akceptuje si tylko jeden).
var uni = new Universe();
var uni2 = new Universe();
uni === uni2; // true
W tym przykadzie
uni
tworzone jest tylko przy pierwszym wywoaniu konstruktora. Drugie
i kolejne wywoania zwracaj ten sam obiekt. Dziki temu
uni === uni2
(to dokadnie ten
sam obiekt). Jak osign taki efekt w jzyku JavaScript?
Konstruktor
Universe
musi zapamita instancj obiektu (
this
), gdy zostanie utworzona po
raz pierwszy, a nastpnie zwraca j przy kolejnych wywoaniach. Istnieje kilka sposobów,
by to uzyska.
x
Wykorzystanie zmiennej globalnej do zapamitania instancji. Nie jest to zalecane podej-
cie, bo zmienne globalne naley tworzy tylko wtedy, gdy jest to naprawd niezbdne.
Co wicej, kady moe nadpisa tak zmienn, take przez przypadek. Na tym zakoczmy
rozwaania dotyczce tej wersji.
x
Wykorzystanie waciwoci statycznej konstruktora. Funkcje w jzyku JavaScript s
obiektami, wic maj waciwoci. Mona by utworzy waciwo
Universe.instance
i to w niej przechowywa obiekt. To eleganckie rozwizanie, ale ma jedn wad: waci-
wo
instance
byaby dostpna publicznie i inny kod mógby j zmieni.
x
Zamknicie instancji w domkniciu. W ten sposób instancja staje si elementem prywatnym
i nie moe zosta zmieniona z zewntrz. Cen tego rozwizania jest dodatkowe domknicie.
Przyjrzyjmy si przykadowym implementacjom drugiej i trzeciej opcji.
Kup książkę
Poleć książkę
Singleton
_ 139
Instancja we waciwoci statycznej
Poniszy kod zapamituje pojedyncz instancj we waciwoci statycznej konstruktora
Universe
.
function Universe() {
// czy istnieje ju instancja?
if (typeof Universe.instance === "object") {
return Universe.instance;
}
// standardowe dziaania
this.start_time = 0;
this.bang = "Wielki";
// zapamitanie instancji
Universe.instance = this;
// niejawna instrukcja return:
// return this;
}
// test
var uni = new Universe();
var uni2 = new Universe();
uni === uni2; // true
To bardzo proste rozwizanie z jedn wad, któr jest publiczne udostpnienie
instance
.
Cho prawdopodobiestwo zmiany takiej waciwoci przez kod jest niewielkie (i na pewno
znaczco mniejsze ni w przypadku zmiennej globalnej), to jednak jest to moliwe.
Instancja w domkniciu
Innym sposobem uzyskania singletonu podobnego do rozwiza klasowych jest uycie
domknicia w celu ochrony instancji. W implementacji mona wykorzysta wzorzec prywat-
nej skadowej statycznej omówiony w rozdziale 5. Tajnym skadnikiem jest nadpisanie kon-
struktora.
function Universe() {
// zapamitanie instancji
var instance = this;
// standardowe dziaania
this.start_time = 0;
this.bang = "Wielki";
// nadpisanie konstruktora
Universe = function () {
return instance;
};
}
// testy
var uni = new Universe();
var uni2 = new Universe();
uni === uni2; // true
Kup książkę
Poleć książkę
140
_
Rozdzia 7. Wzorce projektowe
Za pierwszym razem zostaje wywoany oryginalny konstruktor, który zwraca
this
w sposób
standardowy. Drugie i nastpne wywoania wykonuj ju zmieniony konstruktor, który ma
dostp do zmiennej prywatnej
instance
dziki domkniciu i po prostu j zwraca.
Przedstawiona implementacja jest w zasadzie przykadem wzorca samomodyfikujcej si
funkcji z rozdziau 4. Wad tego rozwizania opisan we wspomnianym rozdziale jest to, e
nadpisana funkcja (w tym przypadku konstruktor
Universe()
) utraci wszystkie waciwoci
dodane midzy jej zdefiniowaniem i nadpisaniem. W tej konkretnej sytuacji nic z tego, co zo-
stanie dodane do prototypu
Universe()
po pierwszym obiekcie, nie bdzie mogo posiada
referencji do instancji utworzonej przez oryginaln implementacj.
Dla uwidocznienia problemu wykonajmy krótki test. Najpierw kilka wierszy przygotowujcych:
// dodanie waciwoci do prototypu
Universe.prototype.nothing = true;
var uni = new Universe();
// ponowne dodanie waciwoci do prototypu
// po utworzeniu pierwszego obiektu
Universe.prototype.everything = true;
var uni2 = new Universe();
Oto waciwy test:
// tylko oryginalny prototyp jest powizany z obiektami
uni.nothing; // true
uni2.nothing; // true
uni.everything; // undefined
uni2.everything; // undefined
// wyglda prawidowo:
uni.constructor.name; // "Universe"
// ale to jest dziwne:
uni.constructor === Universe; // false
Powodem, dla którego waciwo
uni.constructor
nie jest ju taka sama jak konstruktor
Universe()
, jest fakt, i
uni.constructor
nadal wskazuje na oryginalny konstruktor zamiast
przedefiniowanego.
Jeli prototyp i referencja wskazujca na konstruktor musz dziaa prawidowo, do wcze-
niejszej implementacji trzeba wprowadzi kilka poprawek.
function Universe() {
// zapamitanie instancji
var instance;
// nadpisanie konstruktora
Universe = function Universe() {
return instance;
};
// przeniesienie waciwoci prototypu
Universe.prototype = this;
// instancja
instance = new Universe();
// zmiana referencji wskazujcej na konstruktor
instance.constructor = Universe;
Kup książkę
Poleć książkę
Fabryka
_ 141
// waciwa funkcjonalno
instance.start_time = 0;
instance.bang = "Wielki";
return instance;
}
Teraz wszystkie testy powinny dziaa zgodnie z oczekiwaniami.
// aktualizacja prototypu i utworzenie instancji
Universe.prototype.nothing = true; // true
var uni = new Universe();
Universe.prototype.everything = true; // true
var uni2 = new Universe();
// to ta sama pojedyncza instancja
uni === uni2; // true
// wszystkie waciwoci prototypu dziaaj prawidowo
// niezalenie od momentu ich zdefiniowania
uni.nothing && uni.everything && uni2.nothing && uni2.everything; // true
// standardowe waciwoci równie dziaaj prawidowo
uni.bang; // "Wielki"
// referencja wskazujca na konstruktor równie jest prawidowa
uni.constructor === Universe; // true
Alternatywne rozwizanie mogoby polega na otoczeniu konstruktora oraz instancji funkcj
natychmiastow. Pierwsze wywoanie konstruktora tworzy obiekt i zapamituje go w pry-
watnej zmiennej
instance
. Drugie i kolejne wywoania jedynie zwracaj zawarto zmiennej.
Wszystkie poprzednie testy bd dziaay równie dla implementacji przedstawionej poniej.
var Universe;
(function () {
var instance;
Universe = function Universe() {
if (instance) {
return instance;
}
instance = this;
// waciwa funkcjonalno
this.start_time = 0;
this.bang = "Wielki";
};
}());
Fabryka
Celem wzorca fabryki jest tworzenie obiektów. Najczciej fabryk jest klasa lub metoda sta-
tyczna klasy, której celem jest:
x
wykonanie powtarzajcych si operacji przy tworzeniu podobnych obiektów;
x
zapewnienie uytkownikom moliwoci tworzenia obiektów bez potrzeby znania kon-
kretnego typu (klasy) na etapie kompilacji.
Kup książkę
Poleć książkę
142
_
Rozdzia 7. Wzorce projektowe
Drugi punkt ma wiksze znaczenie w przypadku jzyków ze statyczn analiz typów, w któ-
rych to utworzenie instancji klas nieznanych na etapie kompilacji nie jest zadaniem atwym.
Na szczcie w jzyku JavaScript nie trzeba gowi si nad tym zagadnieniem.
Obiekty tworzone przez metod fabryczn z reguy dziedzicz po tym samym przodku, ale
z drugiej strony s wyspecjalizowanymi wersjami z pewnymi dodatkowymi rozwizaniami.
Czasem wspólny przodek to klasa zawierajca metod fabryczn.
Przyjrzyjmy si przykadowej implementacji, która ma:
x
wspólny konstruktor przodka
CarMaker
;
x
metod statyczn
CarMaker
o nazwie
factory()
, która tworzy obiekty samochodów;
x
wyspecjalizowane konstruktory
CarMaker.Compact
,
CarMaker.SUV
i
CarMaker.Convertible
,
które dziedzicz po
CarMaker
i wszystkie s statycznymi waciwociami przodka, dziki
czemu globalna przestrze nazw pozostaje czysta i atwo je w razie potrzeby odnale.
Implementacja bdzie moga by wykorzystywana w nastpujcy sposób:
var corolla = CarMaker.factory('Compact');
var solstice = CarMaker.factory('Convertible');
var cherokee = CarMaker.factory('SUV');
corolla.drive(); // "Brum, mam 4 drzwi"
solstice.drive(); // "Brum, mam 2 drzwi"
cherokee.drive(); // "Brum, mam 17 drzwi"
Fragment
var corolla = CarMaker.factory('Compact');
to prawdopodobnie najbardziej rozpoznawalna cz wzorca fabryki. Metoda przyjmuje typ
jako tekst i na jego podstawie tworzy i zwraca obiekty danego typu. Nie pojawiaj si kon-
struktory wykorzystujce
new
lub literay obiektów — uytkownik stosuje funkcj, która two-
rzy obiekty na podstawie typu wskazanego jako tekst.
Oto przykadowa implementacja wzorca fabryki, która odpowiada wczeniejszemu przyka-
dowi jego uycia:
// konstruktor przodka
function CarMaker() {}
// metoda przodka
CarMaker.prototype.drive = function () {
return "Brum, mam " + this.doors + " drzwi";
};
// statyczna metoda fabryczna
CarMaker.factory = function (type) {
var constr = type,
newcar;
// bd, jeli konstruktor nie istnieje
if (typeof CarMaker[constr] !== "function") {
throw {
name: "Error",
message: constr + " nie istnieje"
};
}
// na tym etapie wiemy, e konstruktor istnieje
// niech odziedziczy przodka, ale tylko raz
Kup książkę
Poleć książkę
Iterator
_ 143
if (typeof CarMaker[constr].prototype.drive !== "function") {
CarMaker[constr].prototype = new CarMaker();
}
// utworzenie nowej instancji
newcar = new CarMaker[constr]();
// opcjonalne wywoanie dodatkowych metod i zwrócenie obiektu...
return newcar;
};
// definicje konkretnych konstruktorów
CarMaker.Compact = function () {
this.doors = 4;
};
CarMaker.Convertible = function () {
this.doors = 2;
};
CarMaker.SUV = function () {
this.doors = 17;
};
W implementacji wzorca fabryki nie ma nic szczególnego. Wystarczy wyszuka odpowiedni
funkcj konstruujc, która utworzy obiekt wymaganego typu. W tym przypadku zastoso-
wano bardzo proste odwzorowanie nazw przekazywanych do fabryki na odpowiadajce im
obiekty. Przykadem powtarzajcych si zada, które warto byoby umieci w fabryce, za-
miast powtarza osobno dla kadego konstruktora, jest dziedziczenie.
Wbudowane fabryki obiektów
W zasadzie jzyk JavaScript posiada wbudowan fabryk, któr jest globalny konstruktor
Object()
. Zachowuje si on jak fabryka, poniewa zwraca róne typy obiektów w zalenoci
od parametru wejciowego. Przekazanie liczby spowoduje utworzenie obiektu konstruktorem
Number()
. Podobnie dzieje si dla tekstów i wartoci logicznych. Wszystkie inne wartoci lub
brak argumentu spowoduj utworzenie zwykego obiektu.
Oto kilka przykadów i testów tego sposobu dziaania. Co wicej,
Object
mona równie
wywoa z takim samym efektem bez uycia
new
.
var o = new Object(),
n = new Object(1),
s = Object('1'),
b = Object(true);
// testy
o.constructor === Object; // true
n.constructor === Number; // true
s.constructor === String; // true
b.constructor === Boolean; // true
To, e
Object()
jest równie fabryk, ma mae znaczenie praktyczne, ale warto o tym
wspomnie, by mie wiadomo, i wzorzec fabryki pojawia si niemal wszdzie.
Iterator
We wzorcu iteratora mamy do czynienia z pewnym obiektem zawierajcym zagregowane
dane. Dane te mog by przechowywane wewntrznie w bardzo zoonej strukturze, ale se-
kwencyjny dostp do nich zapewnia bardzo prosta funkcja. Kod korzystajcy z obiektu nie
musi zna caej zoonoci struktury danych — wystarczy, e wie, jak korzysta z poje-
dynczego elementu i pobra nastpny.
Kup książkę
Poleć książkę
144
_
Rozdzia 7. Wzorce projektowe
We wzorcu iteratora kluczow rol odgrywa metoda
next()
. Kade jej wywoanie powinno
zwraca nastpny element w kolejce. To, jak uoona jest kolejka i jak posortowane s ele-
menty, zaley od zastosowanej struktury danych.
Przy zaoeniu, e obiekt znajduje si w zmiennej
agg
, dostp do wszystkich elementów da-
nych uzyska si dziki wywoywaniu
next()
w ptli:
var element;
while (element = agg.next()) {
// wykonanie dziaa na elemencie...
console.log(element);
}
We wzorcu iteratora bardzo czsto obiekt agregujcy zapewnia dodatkow metod pomocni-
cz
hasNext()
, która informuje uytkownika, czy zosta ju osignity koniec danych. Inny
sposób uzyskania sekwencyjnego dostpu do wszystkich elementów, tym razem z uyciem
hasNext()
, mógby wyglda nastpujco:
while (agg.hasNext()) {
// wykonanie dziaa na nastpnym elemencie...
console.log(agg.next());
}
Po przedstawieniu sposobów uycia wzorca czas na implementacj obiektu agregujcego.
Implementujc wzorzec iteratora, warto w zmiennej prywatnej przechowywa dane oraz
wskanik (indeks) do nastpnego elementu. W naszym przykadzie zaómy, e dane to ty-
powa tablica, a „specjalna” logika pobierania tak naprawd zwraca jej nastpny element.
var agg = (function () {
var index = 0,
data = [1, 2, 3, 4, 5],
length = data.length;
return {
next: function () {
var element;
if (!this.hasNext()) {
return null;
}
element = data[index];
index = index + 2;
return element;
},
hasNext: function () {
return index < length;
}
};
}());
Aby zapewni atwiejszy dostp do danych i moliwo kilkukrotnej iteracji, obiekt moe
oferowa dodatkowe metody:
x
rewind()
— ustawia wskanik na pocztek kolejki;
x
current()
— zwraca aktualny element, bo nie mona tego uczyni za pomoc
next()
bez jednoczesnej zmiany wskanika.
Kup książkę
Poleć książkę
Dekorator
_ 145
Implementacja tych dodatkowych metod nie sprawi adnych trudnoci.
var agg = (function () {
// [jak wyej...]
return {
// [jak wyej...]
rewind: function () {
index = 0;
},
current: function () {
return data[index];
}
};
}());
Oto dodatkowy test iteratora:
// ptla wywietla wartoci 1, 3 i 5
while (agg.hasNext()) {
console.log(agg.next());
}
// powrót na pocztek
agg.rewind();
console.log(agg.current()); // 1
W konsoli pojawi si nastpujce wartoci: 1, 3 i 5 (z ptli), a na kocu ponownie 1 (po
przejciu na pocztek kolejki).
Dekorator
We wzorcu dekoratora dodatkow funkcjonalno mona dodawa do obiektu dynamicznie
w trakcie dziaania programu. W przypadku korzystania ze statycznych i niezmiennych klas
jest to faktycznie due wyzwanie. W jzyku JavaScript obiekty mona modyfikowa, wic
dodanie do nich nowej funkcjonalnoci nie stanowi wielkiego problemu.
Dodatkow cech wzorca dekoratora jest atwo dostosowania i konfiguracji jego oczekiwa-
nego zachowania. Zaczyna si od prostego obiektu z podstawow funkcjonalnoci. Nastp-
nie wybiera si kilka z zestawu dostpnych dekoratorów, po czym rozszerza si nimi pod-
stawowy obiekt. Czasem istotna jest kolejno tego rozszerzania.
Sposób uycia
Przyjrzyjmy si sposobom uycia tego wzorca. Przypumy, e opracowujemy aplikacj, która
co sprzedaje. Kada nowa sprzeda to nowy obiekt
sale
. Obiekt zna cen produktu i potrafi
j zwróci po wywoaniu metody
sale.getPrice()
. W zalenoci od aktualnych warunków
mona zacz „dekorowa” obiekt dodatkow funkcjonalnoci. Wyobramy sobie, e jako
amerykaski sklep sprzedajemy produkt klientowi z kanadyjskiej prowincji Québec. W takiej
sytuacji klient musi zapaci podatek federalny i dodatkowo podatek lokalny. We wzorcu
dekoratora bdziemy wic „dekorowali” obiekt dekoratorem podatku federalnego i dekora-
torem podatku lokalnego. Po wyliczeniu ceny kocowej mona równie doda dekorator do
jej formatowania. Scenariusz byby nastpujcy:
Kup książkę
Poleć książkę
146
_
Rozdzia 7. Wzorce projektowe
var sale = new Sale(100); // cena wynosi 100 dolarów
sale = sale.decorate('fedtax'); // dodaj podatek federalny
sale = sale.decorate('quebec'); // dodaj podatek lokalny
sale = sale.decorate('money'); // formatowanie ceny
sale.getPrice(); // "USD 112.88"
W innym scenariuszu kupujcy moe mieszka w prowincji, która nie stosuje podatku lokal-
nego, i dodatkowo moemy chcie poda cen w dolarach kanadyjskich.
var sale = new Sale(100); // cena wynosi 100 dolarów
sale = sale.decorate('fedtax'); // dodaj podatek federalny
sale = sale.decorate('cdn'); // sformatuj jako dolary kanadyjskie
sale.getPrice(); // "CAD 105.00"
Nietrudno zauway, e jest to wygodny i elastyczny sposób dodawania lub modyfikowania
funkcjonalnoci utworzonych ju obiektów. Czas na implementacj wzorca.
Implementacja
Jednym ze sposobów implementacji wzorca dekoratora jest utworzenie dekoratorów jako
obiektów zawierajcych metody do nadpisania. Kady dekorator dziedziczy wówczas tak
naprawd po obiekcie rozszerzonym przez poprzedni dekorator. Kada dekorowana metoda
wywouje swoj poprzedniczk za pomoc
uber
(odziedziczony obiekt), pobiera warto
i przetwarza j, dodajc co nowego.
Efekt jest taki, e wywoanie metody
sale.getPrice()
z pierwszego z przedstawionych
przykadów powoduje tak naprawd wywoanie metody dekoratora
money
(patrz rysunek 7.1).
Poniewa jednak kady dekorator wywouje najpierw odpowiadajc mu metod ze swego
poprzednika,
getPrice()
z
money
wywouje
getPrice()
z
quebec
, a ta metod
getPrice()
z
fedtax
i tak dalej. acuch moe by duszy, ale koczy si oryginaln metod
getPrice()
zaimplementowan przez konstruktor
Sale()
.
Rysunek 7.1. Implementacja wzorca dekoratora
Kup książkę
Poleć książkę
Dekorator
_ 147
Implementacja rozpoczyna si od konstruktora i metody prototypu.
function Sale(price) {
this.price = price || 100;
}
Sale.prototype.getPrice = function () {
return this.price;
};
Wszystkie obiekty dekoratorów znajd si we waciwoci konstruktora:
Sale.decorators = {};
Przyjrzyjmy si przykadowemu dekoratorowi. To obiekt implementujcy zmodyfikowan
wersj metody
getPrice()
. Metoda najpierw pobiera zwrócon przez metod przodka warto,
a nastpnie j modyfikuje.
Sale.decorators.fedtax = {
getPrice: function () {
var price = this.uber.getPrice();
price += price * 5 / 100;
return price;
}
};
W podobny sposób mona zaimplementowa dowoln liczb innych dekoratorów. Mog one
stanowi rozszerzenie podstawowej funkcjonalnoci
Sale()
, czyli dziaa jak dodatki. Co wicej,
nic nie stoi na przeszkodzie, by znajdoway si w dodatkowych plikach i byy implementowane
przez innych, niezalenych programistów.
Sale.decorators.quebec = {
getPrice: function () {
var price = this.uber.getPrice();
price += price * 7.5 / 100;
return price;
}
};
Sale.decorators.money = {
getPrice: function () {
return "USD " + this.uber.getPrice().toFixed(2);
}
};
Sale.decorators.cdn = {
getPrice: function () {
return "CAD " + this.uber.getPrice().toFixed(2);
}
};
Na koniec przyjrzyjmy si „magicznej” metodzie o nazwie
decorate()
, która czy ze sob
wszystkie elementy. Sposób jej uycia jest nastpujcy:
sale = sale.decorate('fedtax');
Tekst
'fedtax'
odpowiada obiektowi zaimplementowanemu w
Sale.decorators.fedtax
.
Nowy obiekt
newobj
dziedziczy obiekt aktualny (orygina lub ju udekorowan wersj), któ-
ry jest zawarty w
this
. Do zapewnienia dziedziczenia wykorzystajmy wzorzec konstruktora
tymczasowego z poprzedniego rozdziau. Dodatkowo ustawmy waciwo
uber
obiektu
newobj
, by potomek mia dostp do przodka. Nastpnie niech kod kopiuje wszystkie wa-
ciwoci z dekoratora do nowego obiektu i zwraca
newobj
jako wynik caej operacji, co spo-
woduje, e stanie si on nowym obiektem
sale
.
Kup książkę
Poleć książkę
148
_
Rozdzia 7. Wzorce projektowe
Sale.prototype.decorate = function (decorator) {
var F = function () {},
overrides = this.constructor.decorators[decorator],
i, newobj;
F.prototype = this;
newobj = new F();
newobj.uber = F.prototype;
for (i in overrides) {
if (overrides.hasOwnProperty(i)) {
newobj[i] = overrides[i];
}
}
return newobj;
};
Implementacja wykorzystujca list
Przeanalizujmy inn implementacj, która korzysta z dynamicznej natury jzyka JavaScript
i w ogóle nie stosuje dziedziczenia. Dodatkowo, zamiast wymusza na kadej metodzie de-
korujcej, by wywoywaa swoj poprzedniczk, przekazujemy tu wynik poprzedniej metody
jako parametr nastpnej.
Taka implementacja znaczco uatwia wycofanie udekorowania, czyli usunicie jednego
z elementów z listy dekoratorów.
Sposób uycia nowej implementacji bdzie prostszy, bo nie wymaga ona przypisywania
wartoci zwróconej przez
decorate()
. W tym przypadku
decorate()
jedynie dodaje nowy
element do listy:
var sale = new Sale(100); // cena wynosi 100 dolarów
sale.decorate('fedtax'); // dodaj podatek federalny
sale.decorate('quebec'); // dodaj podatek lokalny
sale.decorate('money'); // formatowanie ceny
sale.getPrice(); // "USD 112.88"
Tym razem konstruktor
Sale()
zawiera list dekoratorów jako wasn waciwo.
function Sale(price) {
this.price = (price > 0) || 100;
this.decorators_list = [];
}
Dostpne dekoratory s ponownie implementowane jako waciwoci
Sale.decorators
.
S prostsze, bo nie musz ju wywoywa poprzedniej wersji metody
getPrice()
, by uzy-
ska warto poredni. Teraz trafia ona do systemu jako parametr.
Sale.decorators = {};
Sale.decorators.fedtax = {
getPrice: function (price) {
return price + price * 5 / 100;
}
};
Sale.decorators.quebec = {
getPrice: function (price) {
return price + price * 7.5 / 100;
}
};
Kup książkę
Poleć książkę
Strategia
_ 149
Sale.decorators.money = {
getPrice: function (price) {
return "USD " + price.toFixed(2);
}
};
Interesujce konstrukcje pojawiaj si w metodach
decorate()
i
getPrice()
oryginalnego
obiektu. W poprzedniej implementacji metoda
decorate()
bya w miar zoona, a
getPrice()
niezwykle prosta. W nowej jest dokadnie odwrotnie —
decorate()
po prostu dodaje nowy
element do listy, a
getPrice()
wykonuje ca istotn prac. Prac t jest przejcie przez list
wszystkich dodanych dekoratorów i wywoanie dla kadego z nich metody
getPrice()
z po-
przedni wartoci podan jako argument metody.
Sale.prototype.decorate = function (decorator) {
this.decorators_list.push(decorator);
};
Sale.prototype.getPrice = function () {
var price = this.price,
i,
max = this.decorators_list.length,
name;
for (i = 0; i < max; i += 1) {
name = this.decorators_list[i];
price = Sale.decorators[name].getPrice(price);
}
return price;
};
Druga implementacja jest prostsza i nie korzysta z dziedziczenia. Prostsze s równie metody
dekorujce. Ca rzeczywist prac wykonuje metoda, która „zgadza” si na dekoracj. W tej
prostej implementacji dekoracj dopuszcza jedynie metoda
getPrice()
. Jeli dekoracja mia-
aby dotyczy wikszej liczby metod, kada z nich musiaaby przej przez list dekoratorów
i wywoa odpowiednie metody. Oczywicie taki kod stosunkowo atwo jest umieci
w osobnej metodzie pomocniczej i uogólni. Umoliwiaby on dodanie dekorowalnoci do
dowolnej metody. Co wicej, w takiej implementacji waciwo
decorators_list
byaby
obiektem z waciwociami o nazwach metod i z tablicami dekorowanych obiektów jako
wartociami.
Strategia
Wzorzec strategii umoliwia wybór odpowiedniego algorytmu na etapie dziaania aplikacji.
Uytkownicy kodu mog stosowa ten sam interfejs zewntrzny, ale wybiera sporód kilku
dostpnych algorytmów, by lepiej dopasowa implementacj do aktualnego kontekstu.
Przykadem wzorca strategii moe by rozwizywanie problemu walidacji formularzy. Mona
utworzy jeden obiekt sprawdzania z metod
validate()
. Metoda zostanie wywoana nie-
zalenie od rodzaju formularza i zawsze zwróci ten sam wynik — list danych, które nie s
poprawne, wraz z komunikatami o bdach.
W zalenoci od sprawdzanych danych i typu formularza uytkownik kodu moe wybra
róne rodzaje sprawdze. Walidator wybiera najlepsz strategi wykonania zadania i dele-
guje konkretne czynnoci sprawdze do odpowiednich algorytmów.
Kup książkę
Poleć książkę
150
_
Rozdzia 7. Wzorce projektowe
Przykad walidacji danych
Przypumy, e mamy do czynienia z nastpujcym zestawem danych pochodzcym naj-
prawdopodobniej z formularza i e chcemy go sprawdzi pod ktem poprawnoci:
var data = {
first_name: "Super",
last_name: "Man",
age: "unknown",
username: "o_O"
};
Aby walidator zna najlepsz strategi do zastosowania w tym konkretnym przykadzie, trzeba
najpierw go skonfigurowa, okrelajc zestaw regu i wartoci uznawanych za prawidowe.
Przypumy, e nie wymagamy podania nazwiska i zaakceptujemy dowoln warto imienia,
ale wymagamy podania wieku jako liczby i nazwy uytkownika, która skada si tylko z liczb
i liter bez znaków specjalnych. Konfiguracja mogaby wyglda nastpujco:
validator.config = {
first_name: 'isNonEmpty',
age: 'isNumber',
username: 'isAlphaNum'
};
Po skonfigurowaniu obiektu
validator
jest on gotowy do przyjcia danych. Wywoujemy
jego metod
validate()
i wywietlamy bdy walidacji w konsoli.
validator.validate(data);
if (validator.hasErrors()) {
console.log(validator.messages.join("\n"));
}
Efektem wykonania kodu mógby by nastpujcy komunikat:
Niepoprawna warto *age*; warto musi by liczb, na przykad 1, 3.14 lub 2010
Niepoprawna warto *username*; warto musi zawiera jedynie litery i cyfry bez
adnych znaków specjalnych
Przyjrzyjmy si implementacji walidatora. Poszczególne algorytmy s obiektami o z góry
ustalonym interfejsie — zawieraj metod
validate()
i jednowierszow informacj wyko-
rzystywan jako komunikat o bdzie.
// sprawdzenie, czy podano jak warto
validator.types.isNonEmpty = {
validate: function (value) {
return value !== "";
},
instructions: "warto nie moe by pusta"
};
// sprawdzenie, czy warto jest liczb
validator.types.isNumber = {
validate: function (value) {
return !isNaN(value);
},
instructions: "warto musi by liczb, na przykad 1, 3.14 lub 2010"
};
// sprawdzenie, czy warto zawiera jedynie litery i cyfry
validator.types.isAlphaNum = {
validate: function (value) {
Kup książkę
Poleć książkę
Strategia
_ 151
return !/[^a-z0-9]/i.test(value);
},
instructions: "warto musi zawiera jedynie litery i cyfry bez adnych znaków
´specjalnych"
};
Najwyszy czas na obiekt
validator
:
var validator = {
// wszystkie dostpne sprawdzenia
types: {},
// komunikaty o bdach
// z aktualnej sesji walidacyjnej
messages: [],
// aktualna konfiguracja walidacji
// nazwa => rodzaj testu
config: {},
// metoda interfejsu
// data to pary klucz-warto
validate: function (data) {
var i, msg, type, checker, result_ok;
// usunicie wszystkich komunikatów
this.messages = [];
for (i in data) {
if (data.hasOwnProperty(i)) {
type = this.config[i];
checker = this.types[type];
if (!type) {
continue; // nie trzeba sprawdza
}
if (!checker) { // ojej
throw {
name: "ValidationError",
message: "Brak obsugi dla klucza " + type
};
}
result_ok = checker.validate(data[i]);
if (!result_ok) {
msg = "Niepoprawna warto *" + i + "*; " + checker.instructions;
this.messages.push(msg);
}
}
}
return this.hasErrors();
},
// metoda pomocnicza
hasErrors: function () {
return this.messages.length !== 0;
}
};
Kup książkę
Poleć książkę
152
_
Rozdzia 7. Wzorce projektowe
Obiekt
validator
jest uniwersalny i bdzie dziaa prawidowo dla rónych rodzajów spraw-
dze. Jednym z usprawnie mogoby by dodanie kilku nowych testów. Po wykonaniu kilku
rónych formularzy z walidacj Twoja lista dostpnych sprawdze z pewnoci si wyduy.
Kady kolejny formularz bdzie wymaga jedynie skonfigurowania walidatora i uruchomie-
nia metody
validate()
.
Fasada
Wzorzec fasady jest bardzo prosty i ma za zadanie zapewni alternatywny interfejs obiektu.
Dobr praktyk jest stosowanie krótkich metod, które nie wykonuj zbyt wielu zada. Stosujc
to podejcie, uzyskuje si znacznie wicej metod ni w przypadku tworzenia supermetod z wie-
loma parametrami. W wikszoci sytuacji dwie lub wicej metod wykonuje si jednoczenie.
Warto wtedy utworzy jeszcze jedn metod, która stanowi otoczk dla takich pocze.
W trakcie obsugi zdarze przegldarki bardzo czsto korzysta si z nastpujcych metod:
x
stopPropagation()
— zapobiega wykonywaniu obsugi zdarzenia w wzach nadrzdnych;
x
preventDefault()
— zapobiega wykonaniu przez przegldark domylnej akcji dla zda-
rzenia (na przykad kliknicia cza lub wysania formularza).
To dwie osobne metody wykonujce odmienne zadania, wic nie stanowi jednej caoci, ale z dru-
giej strony w zdecydowanej wikszoci sytuacji s one wykonywane jednoczenie. Zamiast wic
powiela wywoania obu metod w caej aplikacji, mona utworzy fasad, która je obie wykona.
var myevent = {
// ...
stop: function (e) {
e.preventDefault();
e.stopPropagation();
}
// ...
};
Wzorzec fasady przydaje si równie w sytuacjach, w których za fasad warto ukry rónice
pomidzy przegldarkami internetowymi. Nic nie stoi na przeszkodzie, by rozbudowa po-
przedni przykad o inny sposób obsugi anulowania zdarze przez przegldark IE.
var myevent = {
// ...
stop: function (e) {
// inne
if (typeof e.preventDefault === "function") {
e.preventDefault();
}
if (typeof e.stopPropagation === "function") {
e.stopPropagation();
}
// IE
if (typeof e.returnValue === "boolean") {
e.returnValue = false;
}
if (typeof e.cancelBubble === "boolean") {
e.cancelBubble = true;
}
}
// ...
};
Kup książkę
Poleć książkę
Porednik
_ 153
Wzorzec fasady bywa pomocny w przypadku zmiany interfejsów zwizanej na przykad
z refaktoryzacj. Gdy chce si zamieni obiekt na inn implementacj, najczciej moe to
zaj sporo czasu (jeli jest on naprawd rozbudowany). Zaómy te, e ju powstaje kod
dla nowego interfejsu. W takiej sytuacji mona utworzy przed starym obiektem fasad, która
imituje nowy interfejs. W ten sposób po dokonaniu rzeczywistej zamiany i pozbyciu si starego
obiektu ilo zmian w najnowszym kodzie zostanie ograniczona do minimum.
Porednik
We wzorcu projektowym porednika jeden obiekt stanowi interfejs dla innego obiektu. Róni
si to od wzorca fasady, w którym po prostu istniej pewne metody dodatkowe czce
w sobie wywoania kilku innych metod. Porednik znajduje si midzy uytkownikiem
a obiektem i broni dostpu do niego.
Cho wzorzec wyglda jak dodatkowy narzut, w rzeczywistoci czsto suy do poprawy
wydajnoci. Porednik staje si stranikiem rzeczywistego obiektu i stara si, by ten wykona
jak najmniej pracy.
Jednym z przykadów zastosowania porednika jest tak zwana leniwa inicjalizacja. Stosuje
si j w sytuacjach, w których inicjalizacja rzeczywistego obiektu jest kosztowna, a istnieje
spora szansa, e klient po jego zainicjalizowaniu tak naprawd nigdy go nie uyje. Porednik
moe wtedy stanowi interfejs dla rzeczywistego obiektu. Otrzymuje polecenie inicjalizacji,
ale nie przekazuje go dalej a do momentu, gdy rzeczywisty obiekt naprawd zostanie uyty.
Rysunek 7.2 ilustruje sytuacj, w której klient wysya polecenie inicjalizujce, a porednik
odpowiada, e wszystko jest w porzdku, cho tak naprawd nie przekazuje polecenia dalej.
Czeka z inicjalizacj waciwego obiektu do czasu, gdy klient rzeczywicie bdzie wykony-
wa na nim prac — wówczas przekazuje obydwa komunikaty.
Rysunek 7.2. Komunikacja midzy klientem i rzeczywistym obiektem z wykorzystaniem porednika
Przykad
Wzorzec porednika bywa przydatny, gdy rzeczywisty obiekt docelowy wykonuje kosztow-
ne zadanie. W aplikacjach internetowych jedn z kosztownych sytuacji jest danie sieciowe,
wic w miar moliwoci warto zebra kilka operacji i wykona je jednym daniem. Prze-
ledmy praktyczne zastosowanie wzorca wanie w takiej sytuacji.
Kup książkę
Poleć książkę
154
_
Rozdzia 7. Wzorce projektowe
Aplikacja wideo
Zaómy istnienie prostej aplikacji odtwarzajcej materia wideo wybranego artysty (patrz
rysunek 7.3). W zasadzie moesz nawet przetestowa kod, wpisujc w przegldarce interne-
towej adres http://www.jspatterns.com/book/7/proxy.html.
Rysunek 7.3. Aplikacja wideo w akcji
Strona zawiera list tytuów materiaów wideo. Gdy uytkownik kliknie tytu, obszar poniej
rozszerzy si, by przedstawi dodatkowe informacje i umoliwi odtworzenie filmu. Szcze-
góy dotyczce materiaów oraz adres URL treci wideo nie stanowi czci strony — s po-
bierane poprzez osobne wywoania serwera. Serwer przyjmuje jako parametr kilka identyfi-
katorów materiaów wideo, wic aplikacj mona przyspieszy, wykonujc mniej da
HTTP i pobierajc za kadym razem dane kilku filmów.
Aplikacja umoliwia jednoczesne rozwinicie szczegóów kilku (a nawet wszystkich) mate-
riaów, co stanowi doskona okazj do poczenia kilku da w jedno.
Bez uycia porednika
Gównymi elementami aplikacji s dwa obiekty:
x
videos
— jest odpowiedzialny za rozwijanie i zwijanie obszarów informacyjnych (metoda
videos.getInfo()
) oraz za odtwarzanie materiaów wideo (metoda
videos.getPlayer()
).
x
http
— jest odpowiedzialny za komunikacj z serwerem za pomoc metody
http.make
´Request()
.
Kup książkę
Poleć książkę
Porednik
_ 155
Bez stosowania porednika
videos.getInfo()
wywoa
http.makeRequest()
dla kadego
materiau wideo. Po dodaniu porednika pojawi si nowy obiekt o nazwie
proxy
znajdujcy
si midzy
videos
oraz
http
i delegujcy wszystkie wywoania
makeRequest()
, a take -
czcy je ze sob.
Najpierw pojawi si kod, w którym nie zastosowano wzorca porednika. Druga wersja, sto-
sujca obiekt porednika, poprawi ogóln pynno dziaania aplikacji.
Kod HTML
Kod HTML to po prostu zbiór czy.
<p><span id="toggle-all">Przecz zaznaczone</span></p>
<ol id="vids">
<li><input type="checkbox" checked><a
href="http://new.music.yahoo.com/videos/--2158073">Gravedigger</a></li>
<li><input type="checkbox" checked><a
href="http://new.music.yahoo.com/videos/--4472739">Save Me</a></li>
<li><input type="checkbox" checked><a
href="http://new.music.yahoo.com/videos/--45286339">Crush</a></li>
<li><input type="checkbox" checked><a
href="http://new.music.yahoo.com/videos/--2144530">Don't Drink The Water
´</a></li>
<li><input type="checkbox" checked><a
href="http://new.music.yahoo.com/videos/--217241800">Funny the Way It Is
´</a></li>
<li><input type="checkbox" checked><a
href="http://new.music.yahoo.com/videos/--2144532">What Would You Say</a></li>
</ol>
Obsuga zdarze
Zanim pojawi si waciwa obsuga zdarze, warto doda funkcj pomocnicz
$
do pobiera-
nia elementów DOM na podstawie ich identyfikatorów.
var $ = function (id) {
return document.getElementById(id);
};
Stosujc delegacj zdarze (wicej na ten temat w rozdziale 8.), mona obsuy wszystkie
kliknicia dotyczce listy uporzdkowanej
id="vids"
za pomoc jednej funkcji.
$('vids').onclick = function (e) {
var src, id;
e = e || window.event;
src = e.target || e.srcElement;
if (src.nodeName !== "A") {
return;
}
if (typeof e.preventDefault === "function") {
e.preventDefault();
}
e.returnValue = false;
id = src.href.split('--')[1];
if (src.className === "play") {
Kup książkę
Poleć książkę
156
_
Rozdzia 7. Wzorce projektowe
src.parentNode.innerHTML = videos.getPlayer(id);
return;
}
src.parentNode.id = "v" + id;
videos.getInfo(id);
};
Obsuga kliknicia zainteresowana jest tak naprawd dwoma sytuacjami: pierwsz dotyczc
rozwinicia lub zamknicia czci informacyjnej (wywoanie
getInfo()
) i drug zwizan
z odtworzeniem materiau wideo (gdy kliknicie dotyczyo obiektu z klas
play
), co oznacza,
e rozwinicie ju nastpio i mona bezpiecznie wywoa metod
getPlayer()
. Identyfika-
tory materiaów wideo wydobywa si z atrybutów
href
czy.
Druga z funkcji obsugujcych kliknicia dotyczy sytuacji, w której uytkownik chce prze-
czy wszystkie czci informacyjne. W zasadzie sprowadza si ona do wywoywania w ptli
metody
getInfo()
.
$('toggle-all').onclick = function (e) {
var hrefs,
i,
max,
id;
hrefs = $('vids').getElementsByTagName('a');
for (i = 0, max = hrefs.length; i < max; i += 1) {
// pomi cza odtwarzania
if (hrefs[i].className === "play") {
continue;
}
// pomi niezaznaczone
if (!hrefs[i].parentNode.firstChild.checked) {
continue;
}
id = hrefs[i].href.split('--')[1];
hrefs[i].parentNode.id = "v" + id;
videos.getInfo(id);
}
};
Obiekt videos
Obiekt
videos
zawiera trzy metody:
x
getPlayer()
— zwraca kod HTML niezbdny do odtworzenia materiau wideo (nie-
istotny w rozwaaniach na temat obiektu porednika).
x
updateList()
— wywoanie zwrotne otrzymujce wszystkie dane z serwera i generujce
kod HTML do wykorzystania przy rozwijaniu szczegóów filmów (w tej metodzie rów-
nie nie dzieje si nic interesujcego).
x
getInfo()
— metoda przeczajca widoczno czci informacyjnych i wykonu-
jca metody obiektu
http
przez przekazanie
updateList()
jako funkcji wywoania
zwrotnego.
Kup książkę
Poleć książkę
Porednik
_ 157
Oto istotny fragment obiektu
videos
:
var videos = {
getPlayer: function (id) {...},
updateList: function (data) {...},
getInfo: function (id) {
var info = $('info' + id);
if (!info) {
http.makeRequest([id], "videos.updateList");
return;
}
if (info.style.display === "none") {
info.style.display = '';
} else {
info.style.display = 'none';
}
}
};
Obiekt http
Obiekt
http
ma tylko jedn metod, która wykonuje danie JSONP do usugi YQL firmy Yahoo.
var http = {
makeRequest: function (ids, callback) {
var url = 'http://query.yahooapis.com/v1/public/yql?q=',
sql = 'select * from music.video.id where ids IN ("%ID%")',
format = "format=json",
handler = "callback=" + callback,
script = document.createElement('script');
sql = sql.replace('%ID%', ids.join('","'));
sql = encodeURIComponent(sql);
url += sql + '&' + format + '&' + handler;
script.src = url;
document.body.appendChild(script);
}
};
YQL (Yahoo! Query Language) to uogólniona usuga internetowa, która oferuje mo-
liwo korzystania ze skadni przypominajcej SQL do pobierania danych z innych
usug. W ten sposób nie trzeba poznawa szczegóów ich API.
Gdy jednoczenie przeczone zostan wszystkie materiay wideo, do serwera trafi sze
osobnych da; kade bdzie podobne do nastpujcego dania YQL:
select * from music.video.id where ids IN ("2158073")
Obiekt proxy
Zaprezentowany wczeniej kod dziaa prawidowo, ale mona go zoptymalizowa. Na scen
wkracza obiekt
proxy
, który przejmuje komunikacj midzy
http
i
videos
. Obiekt stara si
poczy ze sob kilka da, czekajc na ich zebranie 50 ms. Obiekt
videos
nie wywouje
Kup książkę
Poleć książkę
158
_
Rozdzia 7. Wzorce projektowe
usugi HTTP bezporednio, ale przez porednika. Ten czeka krótk chwil z wysaniem -
dania. Jeli wywoania z
videos
bd przychodziy w odstpach krótszych ni 50 ms, zostan
poczone w jedno danie. Takie opónienie nie jest szczególnie widoczne, ale pomaga zna-
czco przyspieszy dziaanie aplikacji w przypadku jednoczesnego odsaniania wicej ni
jednego materiau wideo. Co wicej, jest równie przyjazne dla serwera, który nie musi ob-
sugiwa sporej liczby da.
Zapytanie YQL dla dwóch materiaów wideo moe mie posta:
select * from music.video.id where ids IN ("2158073", "123456")
W istniejcym kodzie zachodzi tylko jedna zmiana: metoda
videos.getInfo()
wywouje
metod
proxy.makeRequest()
zamiast metody
http.makeRequest()
.
proxy.makeRequest(id, videos.updateList, videos);
Obiekt porednika korzysta z kolejki, w której gromadzi identyfikatory materiaów wideo przeka-
zane w ostatnich 50 ms. Nastpnie przekazuje wszystkie identyfikatory, wywoujc metod obiektu
http
i przekazujc wasn funkcj wywoania zwrotnego, poniewa
videos.updateList()
potrafi
przetworzy tylko pojedynczy rekord danych.
Oto kod obiektu poredniczcego
proxy
:
var proxy = {
ids: [],
delay: 50,
timeout: null,
callback: null,
context: null,
makeRequest: function (id, callback, context) {
// dodanie do kolejki
this.ids.push(id);
this.callback = callback;
this.context = context;
// ustawienie funkcji czasowej
if (!this.timeout) {
this.timeout = setTimeout(function () {
proxy.flush();
}, this.delay);
}
},
flush: function () {
http.makeRequest(this.ids, "proxy.handler");
// wyczyszczenie kolejki i funkcji czasowej
this.timeout = null;
this.ids = [];
},
handler: function (data) {
var i, max;
// pojedynczy materia wideo
if (parseInt(data.query.count, 10) === 1) {
proxy.callback.call(proxy.context, data.query.results.Video);
return;
}
Kup książkę
Poleć książkę
Porednik
_ 159
// kilka materiaów wideo
for (i = 0, max = data.query.results.Video.length; i < max; i += 1) {
proxy.callback.call(proxy.context, data.query.results.Video[i]);
}
}
};
Wprowadzenie porednika umoliwio poczenie kilku da pobrania danych w jedno po-
przez zmian tylko jednego wiersza oryginalnego kodu.
Rysunki 7.4 i 7.5 przedstawiaj scenariusze z trzema osobnymi daniami (bez porednika)
i z jednym poczonym daniem (po uyciu porednika).
Rysunek 7.4. Trzy osobne dania do serwera
Rysunek 7.5. Wykorzystanie porednika do zmniejszenia liczby da wysyanych do serwera
Porednik jako pami podrczna
W prezentowanym przykadzie obiekt
videos
dajcy danych jest na tyle inteligentny, e
nie da tych samych informacji dwukrotnie. Nie zawsze jednak musi tak by. Porednik
moe pój o krok dalej i chroni rzeczywisty obiekt
http
przed powielaniem tych samych
da, zapamitujc je w nowej waciwoci
cache
(patrz rysunek 7.6). Gdyby obiekt
videos
ponownie poprosi o informacje o tym samym materiale (ten sam identyfikator), porednik
wydobyby dane z pamici podrcznej i unikn komunikacji z serwerem.
Rysunek 7.6. Pami podrczna w obiekcie porednika
Kup książkę
Poleć książkę
160
_
Rozdzia 7. Wzorce projektowe
Mediator
Aplikacje — due czy mae — skadaj si z wielu obiektów. Obiekty musz si ze sob ko-
munikowa w sposób, który nie uczyni przyszej konserwacji kodu prawdziw drog przez
mk i umoliwi bezpieczn zmian jednego fragmentu bez potrzeby przepisywania wszyst-
kich innych. Gdy aplikacja si rozrasta, pojawiaj si coraz to nowe obiekty. W trakcie refak-
toryzacji obiekty usuwa si lub przerabia. Gdy wiedz o sobie za duo i komunikuj si
bezporednio (wywouj si wzajemnie i modyfikuj waciwoci), powstaje midzy nimi
niepodany cisy zwizek. Jeli obiekty s ze sob powizane zbyt mocno, nieatwo zmie-
ni jeden z nich bez modyfikacji pozostaych. Wtedy nawet najprostsza zmiana w aplikacji
nie jest duej trywialna i bardzo trudno oszacowa, ile tak naprawd czasu trzeba bdzie
na ni powici.
Wzorzec mediatora ma za zadanie promowa lune powizania obiektów i wspomóc przy-
sz konserwacj kodu (patrz rysunek 7.7). W tym wzorcu niezalene obiekty (koledzy) nie
komunikuj si ze sob bezporednio, ale korzystaj z obiektu mediatora. Gdy jeden z kole-
gów zmieni stan, informuje o tym mediator, a ten przekazuje t informacj wszystkim innym
zainteresowanym kolegom.
Rysunek 7.7. Uczestnicy wzorca mediatora
Przykad mediatora
Przeledmy przykad uycia wzorca mediatora. Aplikacja bdzie gr, w której dwóch gra-
czy przez pó minuty stara si jak najczciej klika w przycisk. Pierwszy gracz naciska kla-
wisz nr 1, a drugi klawisz 0 (spory odstp midzy klawiszami zapewnia, e nie pobij si
o klawiatur). Tablica wyników pokazuje aktualny stan rywalizacji.
Obiektami uczestniczcymi w wymianie informacji s:
x
pierwszy gracz,
x
drugi gracz,
x
tablica,
x
mediator.
Kup książkę
Poleć książkę
Mediator
_ 161
Mediator wie o wszystkich obiektach. Komunikuje si z urzdzeniem wejciowym (klawiatur),
obsuguje nacinicia klawiszy, okrela, który gracz jest aktywny, i informuje o zmianach
wyników (patrz rysunek 7.8). Gracz jedynie gra (czyli aktualizuje swój wasny wynik) i in-
formuje mediator o tym zdarzeniu. Mediator informuje tablic o zmianie wyniku, a ta aktu-
alizuje wywietlan warto.
Rysunek 7.8. Uczestnicy w grze na szybko naciskania klawiszy
Poza mediatorem aden inny obiekt nie wie nic o pozostaych. Dziki temu bardzo atwo
zaktualizowa gr, na przykad doda nowego gracza lub zmieni tablic wyników na wersj
wywietlajc pozostay czas.
Pena wersja gry wraz z kodem ródowym jest dostpna pod adresem http://www.jspatterns.com/
book/7/mediator.html.
Obiekty graczy s tworzone przy uyciu konstruktora
Player()
i zawieraj wasne waci-
woci
points
i
name
. Metoda
play()
z prototypu zwiksza liczb punktów o jeden i infor-
muje o tym fakcie mediator.
function Player(name) {
this.points = 0;
this.name = name;
}
Player.prototype.play = function () {
this.points += 1;
mediator.played();
};
Obiekt
scoreboard
zawiera metod
update()
wywoywan przez mediator po zdobyciu
punktu przez jednego z graczy. Tablica nie wie nic o graczach i nie przechowuje wyniku —
po prostu wywietla informacje przekazane przez mediator.
var scoreboard = {
// aktualizowany element HTML
element: document.getElementById('results'),
// aktualizacja wywietlacza
update: function (score) {
var i, msg = '';
for (i in score) {
if (score.hasOwnProperty(i)) {
Kup książkę
Poleć książkę
162
_
Rozdzia 7. Wzorce projektowe
msg += '<p><strong>' + i + '<\/strong>: ';
msg += score[i];
msg += '<\/p>';
}
}
this.element.innerHTML = msg;
}
};
Czas na obiekt mediatora. Odpowiada on za inicjalizacj gry oraz utworzenie obiektów graczy
w metodzie
setup()
i ledzenie ich poczyna dziki umieszczeniu ich we waciwoci
players
.
Metoda
played()
zostaje wywoana przez kadego z graczy po wykonaniu akcji. Aktualizuje ona
wynik (
score
) i przesya go do tablicy (
scoreboard
). Ostatnia metoda,
keypress()
, obsuguje
zdarzenia klawiatury, okrela, który gracz jest aktywny, i powiadamia go o wykonanej akcji.
var mediator = {
// wszyscy gracze
players: {},
// inicjalizacja
setup: function () {
var players = this.players;
players.home = new Player('Gospodarze');
players.guest = new Player('Gocie');
},
// kto zagra, uaktualnij wynik
played: function () {
var players = this.players,
score = {
"Gospodarze": players.home.points,
"Gocie": players.guest.points
};
scoreboard.update(score);
},
// obsuga interakcji z uytkownikiem
keypress: function (e) {
e = e || window.event; // IE
if (e.which === 49) { // klawisz "1"
mediator.players.home.play();
return;
}
if (e.which === 48) { // klawisz "0"
mediator.players.guest.play();
return;
}
}
};
Ostatni element to uruchomienie i zakoczenie gry.
// start!
mediator.setup();
window.onkeypress = mediator.keypress;
// gra koczy si po 30 sekundach
setTimeout(function () {
window.onkeypress = null;
alert('Koniec gry!');
}, 30000);
Kup książkę
Poleć książkę
Obserwator
_ 163
Obserwator
Wzorzec obserwatora jest niezwykle czsto wykorzystywany w programowaniu po stronie
klienta w jzyku JavaScript. Wszystkie zdarzenia przegldarki (poruszenie mysz, nacinicie
klawisza itp.) to przykady jego uycia. Inn czsto pojawiajc si nazw tego wzorca s
zdarzenia wasne
, czyli zdarzenia tworzone przez programist, a nie przegldark. Jeszcze
inna nazwa to wzorzec subskrybenta-dostawcy.
Gównym celem uywania wzorca jest promowanie lunego powizania elementów. Zamiast
sytuacji, w której jeden obiekt wywouje metod drugiego, mamy sytuacj, w której drugi
z obiektów zgasza ch otrzymywania powiadomie o zmianie w pierwszym obiekcie. Sub-
skrybenta nazywa si czsto obserwatorem, a obiekt obserwowany obiektem publikujcym
lub ródem. Obiekt publikujcy wywouje subskrybentów po zajciu istotnego zdarzenia
i bardzo czsto przekazuje informacj o nim w postaci obiektu zdarzenia.
Pierwszy przykad — subskrypcja magazynu
Aby dowiedzie si, jak zaimplementowa wzorzec, posumy si konkretnym przykadem.
Przypumy, e mamy wydawc
paper
, który publikuje gazet codzienn i miesicznik. Sub-
skrybent
joe
zostanie powiadomiony o wydaniu nowego periodyku.
Obiekt
paper
musi zawiera waciwo
subscribers
, która jest tablic przechowujc
wszystkich subskrybentów. Zgoszenie si do subskrypcji polega jedynie na dodaniu nowego
wpisu do tablicy. Gdy zajdzie istotne zdarzenie, obiekt
paper
przejdzie w ptli przez wszyst-
kich subskrybentów, by ich o nim powiadomi. Notyfikacja polega na wywoaniu metody
obiektu subskrybenta. Oznacza to, e w momencie zgoszenia chci otrzymywania powia-
domie subskrybent musi przekaza obiektowi
paper
jedn ze swoich metod w wywoaniu
metody
subscribe()
.
Obiekt
paper
moe dodatkowo umoliwi anulowanie subskrypcji, czyli usunicie wpisu
z tablicy subskrybentów. Ostatni istotn metod obiektu
paper
jest
publish()
, która wy-
wouje metody subskrybentów. Podsumowujc, obiekt publikujcy musi zawiera nastpujce
skadowe:
x
subscribers
— tablica;
x
subscribe()
— dodaje wpis do tablicy;
x
unsubscribe()
— usuwa wpis z tablicy;
x
publish()
— przechodzi w ptli przez subskrybentów i wywouje przekazane przez
nich metody.
Wszystkie trzy metody potrzebuj parametru
type
, poniewa wydawca moe zgosi kilka
rónych zdarze (publikacj gazety lub magazynu), a subskrybenci mog zdecydowa si na
otrzymywanie powiadomie tylko o jednym z nich.
Poniewa powysze skadowe s bardzo ogólne i mog by stosowane przez dowolnego
wydawc, warto zaimplementowa je jako cz osobnego obiektu. W ten sposób bdzie je
mona w przyszoci skopiowa do dowolnego obiektu, zamieniajc go w wydawc (obiekt
publikujcy).
Kup książkę
Poleć książkę
164
_
Rozdzia 7. Wzorce projektowe
Oto przykadowa implementacja ogólnej funkcjonalnoci obiektu publikujcego, która defi-
niuje wszystkie wymagane skadowe oraz metod pomocnicz
visitSubscribers()
:
var publisher = {
subscribers: {
any: [] // typ zdarzenia
},
subscribe: function (fn, type) {
type = type || 'any';
if (typeof this.subscribers[type] === "undefined") {
this.subscribers[type] = [];
}
this.subscribers[type].push(fn);
},
unsubscribe: function (fn, type) {
this.visitSubscribers('unsubscribe', fn, type);
},
publish: function (publication, type) {
this.visitSubscribers('publish', publication, type);
},
visitSubscribers: function (action, arg, type) {
var pubtype = type || 'any',
subscribers = this.subscribers[pubtype],
i,
max = subscribers.length;
for (i = 0; i < max; i += 1) {
if (action === 'publish') {
subscribers[i](arg);
} else {
if (subscribers[i] === arg) {
subscribers.splice(i, 1);
}
}
}
}
};
Poniej znajduje si kod funkcji, która przyjmuje obiekt i zamienia go w obiekt publikujcy
przez proste skopiowanie wszystkich ogólnych metod dotyczcych publikacji.
function makePublisher(o) {
var i;
for (i in publisher) {
if (publisher.hasOwnProperty(i) && typeof publisher[i] === "function") {
o[i] = publisher[i];
}
}
o.subscribers = {any: []};
}
Czas na implementacj obiektu
paper
, który bdzie publikowa gazet i magazyn.
var paper = {
daily: function () {
this.publish("ciekawy news");
},
monthly: function () {
this.publish("interesujc analiz", "magazyn");
}
};
Trzeba jeszcze uczyni z obiektu wydawc.
makePublisher(paper);
Kup książkę
Poleć książkę
Obserwator
_ 165
Po utworzeniu wydawcy moemy utworzy obiekt subskrybenta o nazwie
joe
, który ma
dwie metody.
var joe = {
drinkCoffee: function (paper) {
console.log('Wanie przeczytaem ' + paper);
},
sundayPreNap: function (monthly) {
console.log('Chyba zasn, czytajc ' + monthly);
}
};
Nastpny krok to obiekt
paper
subskrybujcy
joe
(tak naprawd to
joe
zgasza si jako sub-
skrybent do
paper
).
paper.subscribe(joe.drinkCoffee);
paper.subscribe(joe.sundayPreNap, 'magazyn');
Obiekt
joe
udostpni dwie metody. Pierwsza z nich powinna by wywoywana dla domylnego
zdarzenia „wszystko”, a druga jedynie dla zdarze „magazyn”. Oto kilka zgosze zdarze:
paper.daily();
paper.daily();
paper.daily();
paper.monthly();
Wszystkie metody publikujce wywoay odpowiednie metody z obiektu
joe
, co spowodo-
wao wywietlenie w konsoli nastpujcego wyniku:
Wanie przeczytaem ciekawy news
Wanie przeczytaem ciekawy news
Wanie przeczytaem ciekawy news
Chyba zasn, czytajc interesujc analiz
Bardzo wanym elementem caego systemu jest to, e
paper
nie zawiera w sobie informacji
o
joe
i odwrotnie. Co wicej, nie istnieje obiekt mediatora, który wiedziaby o wszystkich
obiektach. Obiekty uczestniczce w interakcjach s ze sob powizane bardzo luno i bez ja-
kichkolwiek modyfikacji mona doda jeszcze kilku subskrybentów. Co wane,
joe
moe
w dowolnym momencie anulowa subskrypcj.
Nic te nie stoi na przeszkodzie, by
joe
równie zosta wydawc (przecie to nic trudnego
dziki systemom blogowym i mikroblogowym). Jako wydawca
joe
wysya aktualizacj swojego
statusu do serwisu Twitter:
makePublisher(joe);
joe.tweet = function (msg) {
this.publish(msg);
};
Wyobramy sobie, e dzia relacji z klientami wydawcy gazety decyduje si czyta, co o ga-
zecie sdzi jej subskrybent
joe
, i dodaje w tym celu metod
readTweets()
.
paper.readTweets = function (tweet) {
alert('Zwoajmy due zebranie! Kto napisa: ' + tweet);
};
joe.subscribe(paper.readTweets);
Gdy
joe
zamieci swój wpis, wydawca (
paper
) go otrzyma.
joe.tweet("nie lubi tej gazety");
Wykonanie kodu spowoduje wywietlenie w konsoli tekstu „Zwoajmy due zebranie! Kto
napisa: nie lubi tej gazety”.
Kup książkę
Poleć książkę
166
_
Rozdzia 7. Wzorce projektowe
Peny kod ródowy przykadu oraz moliwo sprawdzenia wyników jego dziaania w konsoli
zapewnia strona HTML dostpna pod adresem http://www.jspatterns.com/book/7/observer.html.
Drugi przykad — gra w naciskanie klawiszy
Przyjrzymy si jeszcze jednemu przykadowi. Zaimplementujemy t sam gr w naciskanie
klawiszy co przy wzorcu mediatora, ale tym razem uyjemy wzorca obserwatora. Aby nieco
utrudni zadanie, zapewnijmy obsug dowolnej liczby graczy, a nie tylko dwóch. Ponownie
skorzystamy z konstruktora
Player()
, który tworzy obiekty graczy, i z obiektu
scoreboard
.
Jedynie obiekt
mediator
zamieni si w obiekt
game
.
We wzorcu mediatora obiekt
mediator
wiedzia o wszystkich uczestnikach gry i wywoywa
ich metody. Obiekt
game
ze wzorca obserwatora nie bdzie tego robi — to same obiekty bd
zgaszay ch otrzymywania informacji o zajciu wybranych zdarze. Przykadowo, obiekt
scoreboard
zgosi ch bycia informowanym o zajciu zdarzenia
scorechange
w obiekcie
game
.
Oryginalny obiekt
publisher
naley nieco zmieni, by upodobni go do rozwiza znanych
z przegldarek internetowych.
x
Zamiast metod
publish()
,
subscribe()
i
unsubscribe()
pojawi si metody
fire()
,
on()
i
remove()
.
x
Typ zdarzenia (
type
) bdzie uywany cay czas, wic stanie si pierwszym parametrem
wszystkich trzech funkcji.
x
Dodatkowy parametr
context
przekazywany wraz z funkcj powiadomienia umoliwi
wywoanie funkcji zwrotnej z odpowiednio ustawion wartoci
this
.
Nowy obiekt
publisher
ma nastpujc posta:
var publisher = {
subscribers: {
any: []
},
on: function (type, fn, context) {
type = type || 'any';
fn = typeof fn === "function" ? fn : context[fn];
if (typeof this.subscribers[type] === "undefined") {
this.subscribers[type] = [];
}
this.subscribers[type].push({fn: fn, context: context || this});
},
remove: function (type, fn, context) {
this.visitSubscribers('unsubscribe', type, fn, context);
},
fire: function (type, publication) {
this.visitSubscribers('publish', type, publication);
},
visitSubscribers: function (action, type, arg, context) {
var pubtype = type || 'any',
subscribers = this.subscribers[pubtype],
i,
max = subscribers ? subscribers.length : 0;
for (i = 0; i < max; i += 1) {
if (action === 'publish') {
subscribers[i].fn.call(subscribers[i].context, arg);
} else {
Kup książkę
Poleć książkę
Obserwator
_ 167
if (subscribers[i].fn === arg && subscribers[i].context === context) {
subscribers.splice(i, 1);
}
}
}
}
};
Nowy konstruktor
Player()
wyglda nastpujco:
function Player(name, key) {
this.points = 0;
this.name = name;
this.key = key;
this.fire('newplayer', this);
}
Player.prototype.play = function () {
this.points += 1;
this.fire('play', this);
};
Nowym parametrem przyjmowanym przez konstruktor jest
key
— okrela on klawisz na
klawiaturze, który gracz bdzie naciska, by uzyskiwa punkty (we wczeniejszej wersji kodu
klawisze byy zapisane na sztywno). Dodatkowo utworzenie nowego obiektu gracza powo-
duje zgoszenie zdarzenia
newplayer
, a kade nacinicie klawisza przez gracza skutkuje
zgoszeniem zdarzenia
play
.
Obiekt
scoreboard
pozostaje bez zmian — nadal aktualizuje tablic wyników, korzystajc
z biecych wartoci.
Nowy obiekt
game
potrafi ledzi poczynania wszystkich graczy, by móg zlicza wyniki
i zgasza zdarzenie
scorechange
. Dodatkowo zgasza si on jako subskrybent wszystkich
zdarze
keypress
przegldarki, by wiedzie o wszystkich klawiszach przypisanych poszcze-
gólnym graczom.
var game = {
keys: {},
addPlayer: function (player) {
var key = player.key.toString().charCodeAt(0);
this.keys[key] = player;
},
handleKeypress: function (e) {
e = e || window.event; // IE
if (game.keys[e.which]) {
game.keys[e.which].play();
}
},
handlePlay: function (player) {
var i,
players = this.keys,
score = {};
for (i in players) {
if (players.hasOwnProperty(i)) {
score[players[i].name] = players[i].points;
}
Kup książkę
Poleć książkę
168
_
Rozdzia 7. Wzorce projektowe
}
this.fire('scorechange', score);
}
};
Funkcja
makePublisher()
, która zamieniaa dowolny obiekt w obiekt publikujcy zdarzenia,
jest identyczna jak w przykadzie z wydawc i gazet. Obiekt
game
bdzie zgasza zdarzenia
takie jak
scorechange
. Obiektem publikujcym stanie si równie
Player.prototype
, by moli-
we byo zgaszanie zdarze
play
i
newplayer
wszystkim zainteresowanym.
makePublisher(Player.prototype);
makePublisher(game);
Obiekt
game
zgasza si jako subskrybent zdarze
play
i
newplayer
(a take zdarzenia
keypress
przegldarki), natomiast obiekt
scoreboard
chce by powiadamiany o zdarzeniach
scorechange
.
Player.prototype.on("newplayer", "addPlayer", game);
Player.prototype.on("play", "handlePlay", game);
game.on("scorechange", scoreboard.update, scoreboard);
window.onkeypress = game.handleKeypress;
Metoda
on()
umoliwia subskrybentom okrelenie funkcji zwrotnej jako referencji (
score
´board.update
) lub jako tekstu (
"addPlayer"
). Wersja tekstowa dziaa prawidowo tylko
w przypadku przekazania jako trzeciego parametru kontekstu (na przykad
game
).
Ostatni element to dynamiczne tworzenie tylu obiektów graczy (po naciniciu klawiszy), ile
zostanie zadanych przez grajcych.
var playername, key;
while (1) {
playername = prompt("Dodaj gracza (imi)");
if (!playername) {
break;
}
while (1) {
key = prompt("Klawisz dla gracza " + playername + "?");
if (key) {
break;
}
}
new Player(playername, key);
}
To ju wszystko w temacie gry. Peny kod ródowy wraz z moliwoci zagrania znajduje
si pod adresem http://www.jspatterns.com/book/7/observer-game.html.
W implementacji wzorca mediatora obiekt
mediator
musia wiedzie o wszystkich obiektach,
by móc w odpowiednim czasie wywoywa waciwe metody i koordynowa ca gr. W nowej
implementacji obiekt
game
jest nieco gupszy i wykorzystuje fakt, i obiekty zgaszaj zdarzenia
i obserwuj si nawzajem (na przykad obiekt
scoreboard
nasuchuje zdarzenia
scorechange
).
Zapewnia to jeszcze luniejsze powizanie obiektów (im mniej z nich wie o innych, tym lepiej),
cho za cen utrudnionej analizy, kto tak naprawd nasuchuje kogo. W przykadowej grze
wszystkie subskrypcje s na razie w jednym miejscu, ale gdyby staa si ona bardziej rozbu-
dowana, wywoania
on()
mogyby si znale w wielu rónych miejscach (niekoniecznie
w kodzie inicjalizujcym). Taki kod trudniej jest testowa, gdy trudno od razu zrozumie,
co tak naprawd si w nim dzieje. Wzorzec obserwatora zrywa ze standardowym, proce-
duralnym wykonywaniem kodu od pocztku do koca.
Kup książkę
Poleć książkę
Podsumowanie
_ 169
Podsumowanie
W rozdziale pojawiy si opisy kilku popularnych wzorców projektowych i przykady ich
implementacji w jzyku JavaScript. Omawiane byy nastpujce wzorce:
x
Singleton
— tworzenie tylko jednego obiektu danej „klasy”. Pojawio si kilka rozwi-
za, w tym takie, które staray si zachowa skadni znan z jzyka Java przez zastoso-
wanie funkcji konstruujcych. Trzeba jednak pamita, e z technicznego punktu widze-
nia w jzyku JavaScript wszystkie obiekty s singletonami. Nie naley te zapomina, e
czasem programici stosuj sowo „singleton”, a maj na myli obiekty utworzone przy
uyciu wzorca moduu.
x
Fabryka
— metoda tworzca obiekty o typie przekazanym jako warto tekstowa.
x
Iterator
— interfejs umoliwiajcy atwe przetwarzanie elementów umieszczonych w zo-
onej strukturze danych.
x
Dekorator
— modyfikacja obiektów w trakcie dziaania programu przez dodawanie do
nich funkcjonalnoci zdefiniowanych w dekoratorach.
x
Strategia
— zachowanie tego samego interfejsu przy jednoczesnym wyborze najlepszej
strategii jego implementacji (uzalenionej od kontekstu).
x
Fasada
— zapewnienie bardziej przyjaznego API przez otoczenie typowych (lub le za-
projektowanych) metod ich nowymi wersjami.
x
Porednik
— otoczenie obiektu zapewniajce kontrol dostpu do niego, gdy celem jest
uniknicie wykonywania kosztownych operacji przez ich grupowanie lub opónianie do
momentu, gdy bd naprawd konieczne.
x
Mediator
— promowanie lunego powizania obiektów przez unikanie bezporedniej
komunikacji midzy nimi i zastpienie jej komunikacj poprzez obiekt mediatora.
x
Obserwator
— lune powizanie midzy obiektami osigane przez tworzenie obiektów,
których zmiany mona obserwowa, jawnie zgaszajc si jako subskrybent (czsto mówi
si równie o wasnych zdarzeniach lub wzorcu subskrybenta-dostawcy).
Kup książkę
Poleć książkę
170
_
Rozdzia 7. Wzorce projektowe
Kup książkę
Poleć książkę
195
Skorowidz
.htaccess, 185
@class, 43
@method, 43
@namespace, 43
@param, 41, 43
@return, 41, 43
<script>, 186, 187
dodawanie elementu, 190
dynamiczne elementy, 189
lokalizacja, 187
A
addEventListener(), 175
alert(), 20
antywzorce, 16
Apache, .htaccess, 185
aplikacja
czciowa, 85, 86, 89
funkcji, 84, 85
internetowa, 171
apply(), 85, 133, 134
arguments.callee, 55, 83
Array, 56, 57
asynchroniczne, zdarzenia, 73
atrybut, 17
attachEvent(), 175
B
bbelkowanie zdarze, 177
bind(), 135, 136
break, 32
C
call(), 133, 134
case, 32
CDN, 186
Closure Compiler, 46, 80
console, obiekt, 20
constructor, waciwo, 18, 126
Content Delivery Network, Patrz CDN
Crockford, Douglas, 19, 113
Curry, Haskell, 87
currying, Patrz funkcje, rozwijanie
D
default, 32
dekoratora, wzorzec, 145, 169
implementacja, 146, 147, 148, 149
delegacje zdarze, wzorzec, 177
delete, operator, 24
dir(), 20
Document Object Model, Patrz DOM
dodatki syntaktyczne, 113
dokumentacja, 41
JSDoc, 41
YUIDoc, 41, 42, 44
DOM, 172
dostp, 173
modyfikacja, 174
dorozumiane zmienne globalne, 23, 24
dziedziczenie, 18, 136
klasyczne, 115, 116, 126
nowoczesne, 115, 116
prototypowe, 129, 130
przez kopiowanie waciwoci, 131, 132
wielobazowe, 121
E
ECMAScript 5, 18, 19
dodatki, 130
Error(), 62
ES5, Patrz ECMAScript 5
eval(), 19
unikanie, 32, 33
Expires, nagówek, 185
extend(), 97, 132
extendDeep(), 97
Kup książkę
Poleć książkę
196
_ Skorowidz
F
fabryki, wzorzec, 141, 142, 143, 169
fasady, wzorzec, 152, 153, 169
Firebug, 132
for, ptla, 27, 28
for-in, ptla, 29
Function(), 33, 66
Function.prototype.apply(), 84
funkcje, 17, 65, 66
anonimowe, 66, 68
czasowe, 73
deklaracje, 67, 68
konstruujce, 51, 52
name, waciwo, 68
natychmiastowe, 76, 77, 78, 79, 89
obsugi zdarze asynchronicznych, 73
poredniczce, 126
prywatne, 99
rozwijanie, 84, 86, 87, 89
samodefiniujce si, 75, 76, 90
samowywoujce si, 79
terminologia, 66
waciwoci, 82
wywoania zwrotnego, 70
wywoanie, 85
zwracanie, 74, 89
G
globalne zmienne, 22, 23, 24
dorozumiane, 23, 24
gospodarza, obiekty, 17, 18
H
hasOwnProperty(), 29, 30
hoisting, Patrz przenoszenie deklaracji
HTML, wysyanie pliku fragmentami, 188, 189
HTMLCollection, 27, 28
I
inicjalizacja, 25
leniwa, 153
init(), 79, 80
instanceof, operator, 108
instancja, 115
isArray(), 57
iteratora, wzorzec, 143, 144, 169
J
JavaScript, 15
biblioteki, 94
jako jzyk obiektowy, 16
sprawdzanie jakoci kodu, 19
rodowisko uruchomieniowe, 18
JavaScript Object Notation, Patrz JSON
jQuery, biblioteka, 59, 132
JSDoc, 41
JSLint, 19, 47
JSON, 58
JSON with Padding, Patrz JSONP
JSON.parse(), 58, 59
JSON.stringify(), 59
JSONP, 181, 182, 183
K
klasy, 17, 126
emulacja, 126, 127
kod
konwencje, 34, 35, 36, 37, 38
atwy w konserwacji, 21, 22
minifikowanie, 46
ocenianie przez innych, 45, 46
usuwanie warunkowych wersji, 80, 81, 90
wielokrotne uycie, 115
kodowania, wzorce, 16
komentarze, 40, 41
kompresja, 185
konsola, 20
konstruktory, 54, 119
czyszczenie referencji, 125
poredniczce, 126
poyczanie, 119, 121, 122
samowywoujce, 55
tymczasowe, 124, 126
warto zwracana, 53
konwencje kodu, 34, 35
biae spacje, 37, 38
nawias otwierajcy, 36, 37
nawiasy klamrowe, 35, 36
nazewnictwo, 38, 39, 40, 54
redniki, 37
wcicia, 35
konwersja liczb, 34
kopia
gboka, 131
pytka, 131
Kup książkę
Poleć książkę
Skorowidz
_ 197
L
leniwa inicjalizacja, 153
leniwe wczytywanie, 190, 191
liczby, konwersja, 34
literay
funkcji, 67
obiektów, 49, 50, 51, 98
tablicy, 56
wyraenia regularnego, 59, 60
log(), 20
lokalne zmienne, 22
acuchy wywoa, 112
M
Martin, Robert, 112
mediator, 160
mediatora, wzorzec, 160, 169
przykad, 160, 161, 162
uczestnicy, 160
method(), 113
metody, 17, 49
poyczanie, 133, 134
prywatne, 95, 96
publiczne, 99
statyczne, 107, 108
uprzywilejowane, 96
minifikacja, 46, 185
moduy, 100, 101, 102
import zmiennych globalnych, 103
tworzce konstruktory, 102
N
najmniejszego przywileju, zasada, 97
name, waciwo, 68
natychmiastowa inicjalizacja obiektu, 79, 90
nazewnictwo, konwencje, 38, 39, 40, 54
nazwane wyraenie funkcyjne, 66, 67
new, sowo kluczowe, 54, 138
nienazwane wyraenie funkcyjne, 66, 68
notacja literau obiektu, 49, 50, 51
O
obiekty, 17, 51
bdów, 62
globalne, 22, 25
gospodarza, 17, 18
konfiguracyjne, 83, 84, 89
natychmiastowa inicjalizacja, 79, 90
rdzenne, 17
tworzenie, 51, 91
Object(), 18, 51, 143
Object.create(), 130
Object.prototype.toString(), 58
obserwator, 163
obserwatora, wzorzec, 163, 166, 169
obsuga zdarze, 175, 176
asynchronicznych, 73
onclick, atrybut, 175
open(), 180
P
parseInt(), 34
parseJSON(), 59
ptle
for, 27, 28
for-in, 29
piaskownicy, wzorzec, 103, 104, 105, 114
dodawanie moduów, 105
globalny konstruktor, 104
implementacja konstruktora, 106
porednika, wzorzec, 153, 155, 158, 159, 169
preventDefault(), 152
projektowe, wzorce, 16
prototype, waciwo, 18, 98
modyfikacja, 31
prototypy, 18
acuch, 117, 118, 120, 121
modyfikacja, 31
prywatno, 98
wspódzielenie, 123, 124
prywatno, problemy, 96
przegldarki, wykrywanie, 194
przenoszenie deklaracji, 26, 27
przestrzenie nazw, 22, 91, 92, 114
Firebug, 94
R
ramki, 184
rdzenne obiekty, 17
RegExp(), 59, 60
rzutowanie niejawne, 32
S
Schönfinkel, Moses, 87
schönfinkelizacja, 87
send(), 180
Kup książkę
Poleć książkę
198
_ Skorowidz
serializacja, 82, 83
serwer, komunikacja, 179
setInterval(), 33, 73
setTimeout(), 33, 73, 178
singleton, 137, 138, 169
skadowe
prywatne, 96
statyczne, 107, 109
skrypty
czenie, 184, 185
obliczeniowe, 179
strategie wczytywania, 186
stae, 110, 111
stopPropagation(), 152
strategii, wzorzec, 149, 169
strict mode, Patrz tryb cisy
String.prototype.replace(), 60
styl wielbdzi, 39
subskrybenta-dostawcy, wzorzec, 163, 169
supermetody, 152
switch, 31, 32
SyntaxError(), 62
rodowisko uruchomieniowe, 18
T
that, 54, 55
this, 22, 53
throw, instrukcja, 62
tryb cisy, 19
TypeError(), 62
typeof, 32, 57
typy proste, otoczki, 61, 62
V
var, 23
efekty uboczne pominicia, 24
problem rozrzuconych deklaracji, 26
wzorzec pojedynczego uycia, 25
W
walidacja danych, 150
wtki, symulacja, 178
wczytywanie
leniwe, 190, 191
na danie, 191
wstpne, 192, 193, 194
wielbdzi, styl, 39
window, waciwo, 22, 25
with, polecenie, 19
waciwoci, 17, 49
prywatne, 95, 96
statyczne, 107, 110
wydajno, 184
wyliczenie, 29
wyraenia regularne, 59
wyraenie funkcyjne, 66, 67
nazwane, 66, 67
nienazwane, 66
wywoanie funkcji, 85
wywoanie jako obraz, 184
wywoanie zwrotne, 70, 71, 89
w bibliotekach, 74
zakres zmiennych, 72
wzorce, 11, 15
antywzorce, 16
API, 89
inicjalizacyjne, 89
kodowania, 16
optymalizacyjne, 90
projektowe, 16
X
XHR, Patrz XMLHttpRequest
XMLHttpRequest, 180, 181
Y
Y.clone(), 132
Y.delegate(), 178
Yahoo! Query Language, Patrz YQL
YQL, 157
YUI3, 132, 178
YUIDoc, 41, 42, 44
przykad dokumentacji, 42, 44
Z
zdarzenia, 175
asynchroniczne, 73
delegacje, 177
obsuga, 175, 176
wasne, 163
zmienne, 17
globalne, 22, 23, 24, 103
lokalne, 22
Kup książkę
Poleć książkę