Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00417 007081 12426332 na godz. na dobę w sumie
JavaScript. Wzorce - książka
JavaScript. Wzorce - książka
Autor: Liczba stron: 200
Wydawca: Helion Język publikacji: polski
ISBN: 978-83-246-3821-5 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> webmasterstwo >> javascript - programowanie
Porównaj ceny (książka, ebook, audiobook).

Twórz lepszy kod JavaScript!

Jakie jest najlepsze podejście do tworzenia aplikacji w języku JavaScript? Z tą książką, zawierającą najlepsze praktyki i wiele wzorców kodowania, znajdziesz odpowiedź na to pytanie. Jeśli jesteś doświadczonym programistą szukającym sposobów rozwiązania problemów związanych z obiektami, funkcjami, dziedziczeniem i innymi aspektami języka, przedstawione tu abstrakcje i szablony sprawdzą się idealnie.

Książka napisana przez eksperta języka JavaScript Stoyana Stefanova - starszego inżyniera Yahoo! i architekta narzędzia do optymalizacji stron WWW YSlow 2.0 - zawiera wiele praktycznych wskazówek w zakresie implementacji opisywanych wzorców, a także kilka całościowych przykładów. Znajdziesz w niej również opis wielu antywzorców, czyli podejść, które tak naprawdę powodują więcej problemów, niż są tego warte.

Dowiedz się, jak:

Poznaj tajniki tworzenia łatwego w utrzymaniu kodu źródłowego!


Znajdź podobne książki Ostatnio czytane w tej kategorii

Darmowy fragment publikacji:

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. • Kup książkę • Poleć książkę • Oceń książkę • Księgarnia internetowa • Lubię to! » Nasza społeczność Spis treļci Wstýp ............................................................................................................................11 Wzorce JavaScript — podstawowe cechy 1. Wprowadzenie ............................................................................................................ 15 15 16 16 17 18 18 18 19 20 Zorientowany obiektowo Brak klas Prototypy ćrodowisko ECMAScript 5 Narzödzie JSLint Konsola Tworzenie kodu äatwego w konserwacji Minimalizacja liczby zmiennych globalnych Problem ze zmiennymi globalnymi Efekty uboczne pominiöcia var Dostöp do obiektu globalnego Wzorzec pojedynczego var Przenoszenie deklaracji — problem rozrzuconych deklaracji var 2. Podstawy ..................................................................................................................... 21 21 22 22 24 25 25 26 27 29 31 31 32 32 34 Pötle for Pötle for-in Modyfikacja wbudowanych prototypów Wzorzec konstrukcji switch Unikanie niejawnego rzutowania Unikanie eval() Konwertowanie liczb funkcjñ parseInt() 5 Konwencje dotyczñce kodu Wciöcia Nawiasy klamrowe PoäoĔenie nawiasu otwierajñcego Biaäe spacje Konwencje nazewnictwa Konstruktory pisane od wielkiej litery Oddzielanie wyrazów Inne wzorce nazewnictwa Pisanie komentarzy Pisanie dokumentacji interfejsów programistycznych Przykäad dokumentacji YUIDoc Pisanie w sposób uäatwiajñcy czytanie Ocenianie kodu przez innych czäonków zespoäu Minifikowanie kodu tylko w systemie produkcyjnym Uruchamiaj narzödzie JSLint Podsumowanie 34 35 35 36 37 38 38 39 39 40 41 42 44 45 46 47 47 Literaä obiektu Wäasne funkcje konstruujñce WartoĈè zwracana przez konstruktor Wzorce wymuszania uĔycia new Skäadnia literaäu obiektowego Obiekty z konstruktora Puäapka konstruktora Object Konwencja nazewnictwa UĔycie that Samowywoäujñcy siö konstruktor 3. Literaĥy i konstruktory .................................................................................................49 49 50 51 51 52 53 54 54 54 55 56 56 56 57 58 58 59 60 61 62 63 Skäadnia literaäu tablicy Puäapka konstruktora Array Sprawdzanie, czy obiekt jest tablicñ Otoczki typów prostych Obiekty bäödów Podsumowanie Literaä tablicy JSON Korzystanie z formatu JSON Literaä wyraĔenia regularnego Skäadnia literaäowego wyraĔenia regularnego 6 _ Spis treļci Informacje ogólne Wzorzec wywoäania zwrotnego Zwracanie funkcji Samodefiniujñce siö funkcje Funkcje natychmiastowe Przykäad wywoäania zwrotnego Wywoäania zwrotne a zakres zmiennych Funkcje obsäugi zdarzeþ asynchronicznych Funkcje czasowe Wywoäania zwrotne w bibliotekach Stosowana terminologia Deklaracje kontra wyraĔenia — nazwy i przenoszenie na poczñtek WäaĈciwoĈè name funkcji Przenoszenie deklaracji funkcji 4. Funkcje .........................................................................................................................65 65 66 67 68 68 70 70 72 73 73 74 74 75 76 77 77 79 79 80 82 83 84 84 85 87 89 89 Natychmiastowa inicjalizacja obiektu Usuwanie warunkowych wersji kodu WäaĈciwoĈci funkcji — wzorzec zapamiötywania Obiekty konfiguracyjne Rozwijanie funkcji Parametry funkcji natychmiastowych WartoĈci zwracane przez funkcje natychmiastowe Zalety i zastosowanie Aplikacja funkcji Aplikacja czöĈciowa Rozwijanie funkcji Kiedy uĔywaè aplikacji czöĈciowej Podsumowanie Wzorzec przestrzeni nazw Funkcja przestrzeni nazw ogólnego stosowania Deklarowanie zaleĔnoĈci Metody i wäaĈciwoĈci prywatne 5. Wzorce tworzenia obiektów ...................................................................................... 91 91 92 94 95 96 96 96 98 98 99 Skäadowe prywatne Metody uprzywilejowane Problemy z prywatnoĈciñ Literaäy obiektów a prywatnoĈè Prototypy a prywatnoĈè Udostöpnianie funkcji prywatnych jako metod publicznych Spis treļci _ 7 Wzorzec moduäu Odkrywczy wzorzec moduäu Moduäy, które tworzñ konstruktory Import zmiennych globalnych do moduäu Wzorzec piaskownicy Globalny konstruktor Dodawanie moduäów Implementacja konstruktora Skäadowe statyczne Publiczne skäadowe statyczne Prywatne skäadowe statyczne Staäe obiektów Wzorzec äaþcucha wywoäaþ Wady i zalety wzorca äaþcucha wywoäaþ Metoda method() Podsumowanie 100 102 102 103 103 104 105 106 107 107 109 110 112 112 113 114 PodñĔanie wzdäuĔ äaþcucha prototypów Wady wzorca numer jeden Drugi wzorzec klasyczny — poĔyczanie konstruktora Trzeci wzorzec klasyczny — poĔyczanie i ustawianie prototypu Czwarty wzorzec klasyczny — wspóädzielenie prototypu Piñty wzorzec klasyczny — konstruktor tymczasowy Klasyczne i nowoczesne wzorce dziedziczenia Oczekiwane wyniki w przypadku stosowania wzorca klasycznego Pierwszy wzorzec klasyczny — wzorzec domyĈlny ãaþcuch prototypów Dziedziczenie wielobazowe przy uĔyciu poĔyczania konstruktorów Zalety i wady wzorca poĔyczania konstruktora 6. Wzorce wielokrotnego uŜycia kodu ..........................................................................115 115 116 117 117 119 119 120 121 122 122 123 124 125 125 126 129 129 130 131 132 133 134 134 135 136 Dyskusja Dodatki do standardu ECMAScript 5 Dziedziczenie przez kopiowanie wäaĈciwoĈci Wzorzec wmieszania PoĔyczanie metod Zapamiötywanie klasy nadrzödnej Czyszczenie referencji na konstruktor PodejĈcie klasowe Dziedziczenie prototypowe Przykäad — poĔyczenie metody od obiektu Array PoĔyczenie i przypisanie Metoda Function.prototype.bind() Podsumowanie 8 _ Spis treļci Singleton Fabryka Iterator Dekorator Wbudowane fabryki obiektów UĔycie säowa kluczowego new Instancja we wäaĈciwoĈci statycznej Instancja w domkniöciu Sposób uĔycia Implementacja Implementacja wykorzystujñca listö 7. Wzorce projektowe ....................................................................................................137 137 138 139 139 141 143 143 145 145 146 148 149 150 152 153 153 159 160 160 163 163 166 169 Pierwszy przykäad — subskrypcja magazynu Drugi przykäad — gra w naciskanie klawiszy Przykäad PoĈrednik jako pamiöè podröczna Przykäad walidacji danych Strategia Fasada PoĈrednik Podsumowanie Mediator Przykäad mediatora Obserwator Zdarzenia Dostöp do DOM Modyfikacja DOM Podziaä zadaþ Skrypty wykorzystujñce DOM 8. DOM i wzorce dotyczéce przeglédarek .....................................................................171 171 172 173 174 175 175 177 178 178 179 179 180 181 184 Däugo dziaäajñce skrypty Funkcja setTimeout() Skrypty obliczeniowe Komunikacja z serwerem Obiekt XMLHttpRequest JSONP Ramki i wywoäania jako obrazy Obsäuga zdarzeþ Delegacja zdarzeþ Spis treļci _ 9 Serwowanie kodu JavaScript klientom ãñczenie skryptów Minifikacja i kompresja Nagäówek Expires Wykorzystanie CDN Strategie wczytywania skryptów Lokalizacja elementu script Wysyäanie pliku HTML fragmentami Dynamiczne elementy script zapewniajñce nieblokujñce pobieranie Wczytywanie leniwe Wczytywanie na Ĕñdanie Wstöpne wczytywanie kodu JavaScript Podsumowanie 184 184 185 185 186 186 187 188 189 190 191 192 194 Skorowidz .................................................................................................................. 195 10 _ Spis treļci ROZDZIAĤ 7. Wzorce projektowe Wzorce projektowe opisane w ksiñĔce tak zwanego gangu czworga oferujñ rozwiñzania ty- powych problemów zwiñzanych z projektowaniem oprogramowania zorientowanego obiektowo. Sñ dostöpne juĔ od jakiegoĈ czasu i sprawdziäy siö w wielu róĔnych sytuacjach, warto wiöc siö z nimi zapoznaè i poĈwiöciè im nieco czasu. Choè same te wzorce projektowe nie sñ uzaleĔnione od jözyka programowania i implementa- cji, byäy analizowane przez wiele lat gäównie z perspektywy jözyków o silnym sprawdzaniu typów i statycznych (niezmiennych) klasach takich jak Java lub C++. JavaScript jest jözykiem o luĒnej kontroli typów i bazuje na prototypach (a nie klasach), wiöc nie- które z tych wzorców okazujñ siö wyjñtkowo proste, a czasem wröcz banalne w implementacji. Zacznijmy od przykäadu sytuacji, w której w jözyku JavaScript rozwiñzanie wyglñda inaczej niĔ w przypadku jözyków statycznych bazujñcych na klasach, czyli od wzorca singletonu. Singleton Wzorzec singletonu ma w zaäoĔeniu zapewniè tylko jednñ instancjö danej klasy. Oznacza to, Ĕe próba utworzenia obiektu danej klasy po raz drugi powinna zwróciè dokäadnie ten sam obiekt, który zostaä zwrócony za pierwszym razem. Jak zastosowaè ten wzorzec w jözyku JavaScript? Nie mamy przecieĔ klas, a jedynie obiekty. Gdy powstaje nowy obiekt, nie ma w zasadzie drugiego identycznego, wiöc jest on automa- tycznie singletonem. Utworzenie prostego obiektu za pomocñ literaäu to doskonaäy przykäad utworzenia singletonu. var obj = { myprop: wartoŁð }; W JavaScripcie obiekty nie sñ sobie równe, jeĈli nie sñ dokäadnie tym samym obiektem, wiöc nawet jeĈli utworzy siö dwa identyczne obiekty z takimi samymi wartoĈciami, nie bödñ równowaĔne. var obj2 = { myprop: wartoŁð }; obj === obj2; // false obj == obj2; // false 137 MoĔna wiöc stwierdziè, Ĕe za kaĔdym razem, gdy powstaje nowy obiekt tworzony za pomocñ literaäu, powstaje nowy singleton, i to bez uĔycia dodatkowej skäadni. Czasem gdy ludzie mówiñ „singleton” w kontekĈcie jözyka JavaScript, majñ na myĈli wzorzec moduäu opisany w rozdziale 5. UŜycie sĥowa kluczowego new JavaScript jest jözykiem niestosujñcym klas, wiöc dosäowna definicja singletonu nie ma tu za- stosowania. Z drugiej strony jözyk posiada säowo kluczowe new, które tworzy obiekty na podstawie funkcji konstruujñcych. Czasem tworzenie ich w ten sposób jako singletonów mo- Ĕe byè ciekawym podejĈciem. Ogólny pomysä jest nastöpujñcy: kilkukrotne wywoäanie funkcji konstruujñcej z uĔyciem new powinno spowodowaè kaĔdorazowo zwrócenie dokäadnie tego samego obiektu. Przedstawiony poniĔej opis nie jest uĔyteczny w praktyce. Stanowi raczej teoretyczne wyjaĈnienie powodów powstania wzorca w jözykach statycznych o Ĉcisäej kontroli typów, w których to funkcje nie sñ peänoprawnymi obiektami. PoniĔszy przykäad ilustruje oczekiwane zachowanie (pod warunkiem Ĕe nie wierzy siö w Ĉwiaty równolegäe i akceptuje siö tylko jeden). var uni = new Universe(); var uni2 = new Universe(); uni === uni2; // true W tym przykäadzie uni tworzone jest tylko przy pierwszym wywoäaniu konstruktora. Drugie i kolejne wywoäania zwracajñ ten sam obiekt. Dziöki temu uni === uni2 (to dokäadnie ten sam obiekt). Jak osiñgnñè taki efekt w jözyku JavaScript? Konstruktor Universe musi zapamiötaè instancjö obiektu (this), gdy zostanie utworzona po raz pierwszy, a nastöpnie zwracaè jñ przy kolejnych wywoäaniach. Istnieje kilka sposobów, by to uzyskaè. x Wykorzystanie zmiennej globalnej do zapamiötania instancji. Nie jest to zalecane podej- Ĉcie, bo zmienne globalne naleĔy tworzyè tylko wtedy, gdy jest to naprawdö niezbödne. Co wiöcej, kaĔdy moĔe nadpisaè takñ zmiennñ, takĔe przez przypadek. Na tym zakoþczmy rozwaĔania dotyczñce tej wersji. x Wykorzystanie wäaĈciwoĈci statycznej konstruktora. Funkcje w jözyku JavaScript sñ obiektami, wiöc majñ wäaĈciwoĈci. MoĔna by utworzyè wäaĈciwoĈè Universe.instance i to w niej przechowywaè obiekt. To eleganckie rozwiñzanie, ale ma jednñ wadö: wäaĈci- woĈè instance byäaby dostöpna publicznie i inny kod mógäby jñ zmieniè. x Zamkniöcie instancji w domkniöciu. W ten sposób instancja staje siö elementem prywatnym i nie moĔe zostaè zmieniona z zewnñtrz. Cenñ tego rozwiñzania jest dodatkowe domkniöcie. Przyjrzyjmy siö przykäadowym implementacjom drugiej i trzeciej opcji. 138 _ Rozdziaĥ 7. Wzorce projektowe Instancja we wĥaļciwoļci statycznej PoniĔszy kod zapamiötuje pojedynczñ instancjö we wäaĈciwoĈci statycznej konstruktora Universe. function Universe() { // czy istnieje juĪ instancja? if (typeof Universe.instance === object ) { return Universe.instance; } // standardowe dziaáania this.start_time = 0; this.bang = Wielki ; // zapamiĊtanie instancji Universe.instance = this; // niejawna instrukcja return: // return this; } // test var uni = new Universe(); var uni2 = new Universe(); uni === uni2; // true To bardzo proste rozwiñzanie z jednñ wadñ, którñ jest publiczne udostöpnienie instance. Choè prawdopodobieþstwo zmiany takiej wäaĈciwoĈci przez kod jest niewielkie (i na pewno znaczñco mniejsze niĔ w przypadku zmiennej globalnej), to jednak jest to moĔliwe. Instancja w domkniýciu Innym sposobem uzyskania singletonu podobnego do rozwiñzaþ klasowych jest uĔycie domkniöcia w celu ochrony instancji. W implementacji moĔna wykorzystaè wzorzec prywat- nej skäadowej statycznej omówiony w rozdziale 5. Tajnym skäadnikiem jest nadpisanie kon- struktora. function Universe() { // zapamiĊtanie instancji var instance = this; // standardowe dziaáania 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 Singleton _ 139 Za pierwszym razem zostaje wywoäany oryginalny konstruktor, który zwraca this w sposób standardowy. Drugie i nastöpne wywoäania wykonujñ juĔ zmieniony konstruktor, który ma dostöp do zmiennej prywatnej instance dziöki domkniöciu i po prostu jñ zwraca. Przedstawiona implementacja jest w zasadzie przykäadem wzorca samomodyfikujñcej siö funkcji z rozdziaäu 4. Wadñ tego rozwiñzania opisanñ we wspomnianym rozdziale jest to, Ĕe nadpisana funkcja (w tym przypadku konstruktor Universe()) utraci wszystkie wäaĈciwoĈci dodane miödzy jej zdefiniowaniem i nadpisaniem. W tej konkretnej sytuacji nic z tego, co zo- stanie dodane do prototypu Universe() po pierwszym obiekcie, nie bödzie mogäo posiadaè referencji do instancji utworzonej przez oryginalnñ implementacjö. Dla uwidocznienia problemu wykonajmy krótki test. Najpierw kilka wierszy przygotowujñcych: // dodanie wáaĞciwoĞci do prototypu Universe.prototype.nothing = true; var uni = new Universe(); // ponowne dodanie wáaĞciwoĞci do prototypu // po utworzeniu pierwszego obiektu Universe.prototype.everything = true; var uni2 = new Universe(); Oto wäaĈciwy test: // tylko oryginalny prototyp jest powiązany z obiektami uni.nothing; // true uni2.nothing; // true uni.everything; // undefined uni2.everything; // undefined // wygląda prawidáowo: uni.constructor.name; // Universe // ale to jest dziwne: uni.constructor === Universe; // false Powodem, dla którego wäaĈciwoĈè uni.constructor nie jest juĔ taka sama jak konstruktor Universe(), jest fakt, iĔ uni.constructor nadal wskazuje na oryginalny konstruktor zamiast przedefiniowanego. JeĈli prototyp i referencja wskazujñca na konstruktor muszñ dziaäaè prawidäowo, do wcze- Ĉniejszej implementacji trzeba wprowadziè kilka poprawek. function Universe() { // zapamiĊtanie instancji var instance; // nadpisanie konstruktora Universe = function Universe() { return instance; }; // przeniesienie wáaĞciwoĞci prototypu Universe.prototype = this; // instancja instance = new Universe(); // zmiana referencji wskazującej na konstruktor instance.constructor = Universe; 140 _ Rozdziaĥ 7. Wzorce projektowe // wáaĞciwa funkcjonalnoĞü instance.start_time = 0; instance.bang = Wielki ; return instance; } Teraz wszystkie testy powinny dziaäaè 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 wáaĞciwoĞci prototypu dziaáają prawidáowo // niezaleĪnie od momentu ich zdefiniowania uni.nothing uni.everything uni2.nothing uni2.everything; // true // standardowe wáaĞciwoĞci równieĪ dziaáają prawidáowo uni.bang; // Wielki // referencja wskazująca na konstruktor równieĪ jest prawidáowa uni.constructor === Universe; // true Alternatywne rozwiñzanie mogäoby polegaè na otoczeniu konstruktora oraz instancji funkcjñ natychmiastowñ. Pierwsze wywoäanie konstruktora tworzy obiekt i zapamiötuje go w pry- watnej zmiennej instance. Drugie i kolejne wywoäania jedynie zwracajñ zawartoĈè zmiennej. Wszystkie poprzednie testy bödñ dziaäaäy równieĔ dla implementacji przedstawionej poniĔej. var Universe; (function () { var instance; Universe = function Universe() { if (instance) { return instance; } instance = this; // wáaĞciwa funkcjonalnoĞü this.start_time = 0; this.bang = Wielki ; }; }()); Fabryka Celem wzorca fabryki jest tworzenie obiektów. NajczöĈciej fabrykñ jest klasa lub metoda sta- tyczna klasy, której celem jest: x wykonanie powtarzajñcych siö operacji przy tworzeniu podobnych obiektów; x zapewnienie uĔytkownikom moĔliwoĈci tworzenia obiektów bez potrzeby znania kon- kretnego typu (klasy) na etapie kompilacji. Fabryka _ 141 Drugi punkt ma wiöksze znaczenie w przypadku jözyków ze statycznñ analizñ typów, w któ- rych to utworzenie instancji klas nieznanych na etapie kompilacji nie jest zadaniem äatwym. Na szczöĈcie w jözyku JavaScript nie trzeba gäowiè siö nad tym zagadnieniem. Obiekty tworzone przez metodö fabrycznñ z reguäy dziedziczñ po tym samym przodku, ale z drugiej strony sñ wyspecjalizowanymi wersjami z pewnymi dodatkowymi rozwiñzaniami. Czasem wspólny przodek to klasa zawierajñca metodö fabrycznñ. Przyjrzyjmy siö przykäadowej 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 wäaĈciwoĈciami przodka, dziöki czemu globalna przestrzeþ nazw pozostaje czysta i äatwo je w razie potrzeby odnaleĒè. Implementacja bödzie mogäa byè wykorzystywana w nastöpujñcy 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 wykorzystujñce new lub literaäy obiektów — uĔytkownik stosuje funkcjö, która two- rzy obiekty na podstawie typu wskazanego jako tekst. Oto przykäadowa implementacja wzorca fabryki, która odpowiada wczeĈniejszemu przykäa- dowi jego uĔycia: // 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; // báąd, jeĞli 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 142 _ Rozdziaĥ 7. Wzorce projektowe if (typeof CarMaker[constr].prototype.drive !== function ) { CarMaker[constr].prototype = new CarMaker(); } // utworzenie nowej instancji newcar = new CarMaker[constr](); // opcjonalne wywoáanie 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ö konstruujñcñ, która utworzy obiekt wymaganego typu. W tym przypadku zastoso- wano bardzo proste odwzorowanie nazw przekazywanych do fabryki na odpowiadajñce im obiekty. Przykäadem powtarzajñcych siö zadaþ, które warto byäoby umieĈciè w fabryce, za- miast powtarzaè osobno dla kaĔdego konstruktora, jest dziedziczenie. Wbudowane fabryki obiektów W zasadzie jözyk JavaScript posiada wbudowanñ fabrykö, którñ jest globalny konstruktor Object(). Zachowuje siö on jak fabryka, poniewaĔ zwraca róĔne typy obiektów w zaleĔnoĈci od parametru wejĈciowego. Przekazanie liczby spowoduje utworzenie obiektu konstruktorem Number(). Podobnie dzieje siö dla tekstów i wartoĈci logicznych. Wszystkie inne wartoĈci lub brak argumentu spowodujñ utworzenie zwykäego obiektu. Oto kilka przykäadów i testów tego sposobu dziaäania. Co wiöcej, Object moĔna równieĔ wywoäaè z takim samym efektem bez uĔycia 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 maäe znaczenie praktyczne, ale warto o tym wspomnieè, by mieè ĈwiadomoĈè, iĔ wzorzec fabryki pojawia siö niemal wszödzie. Iterator We wzorcu iteratora mamy do czynienia z pewnym obiektem zawierajñcym zagregowane dane. Dane te mogñ byè przechowywane wewnötrznie w bardzo zäoĔonej strukturze, ale se- kwencyjny dostöp do nich zapewnia bardzo prosta funkcja. Kod korzystajñcy z obiektu nie musi znaè caäej zäoĔonoĈci struktury danych — wystarczy, Ĕe wie, jak korzystaè z poje- dynczego elementu i pobraè nastöpny. Iterator _ 143 We wzorcu iteratora kluczowñ rolö odgrywa metoda next(). KaĔde jej wywoäanie powinno zwracaè nastöpny element w kolejce. To, jak uäoĔona jest kolejka i jak posortowane sñ ele- menty, zaleĔy od zastosowanej struktury danych. Przy zaäoĔeniu, Ĕe obiekt znajduje siö w zmiennej agg, dostöp do wszystkich elementów da- nych uzyska siö dziöki wywoäywaniu next() w pötli: var element; while (element = agg.next()) { // wykonanie dziaáaĔ na elemencie... console.log(element); } We wzorcu iteratora bardzo czösto obiekt agregujñcy zapewnia dodatkowñ metodö pomocni- czñ hasNext(), która informuje uĔytkownika, czy zostaä juĔ osiñgniöty koniec danych. Inny sposób uzyskania sekwencyjnego dostöpu do wszystkich elementów, tym razem z uĔyciem hasNext(), mógäby wyglñdaè nastöpujñco: while (agg.hasNext()) { // wykonanie dziaáaĔ na nastĊpnym elemencie... console.log(agg.next()); } Po przedstawieniu sposobów uĔycia wzorca czas na implementacjö obiektu agregujñcego. Implementujñc wzorzec iteratora, warto w zmiennej prywatnej przechowywaè dane oraz wskaĒnik (indeks) do nastöpnego elementu. W naszym przykäadzie zaäóĔmy, Ĕe dane to ty- powa tablica, a „specjalna” logika pobierania tak naprawdö zwraca jej nastöpny 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 dostöp do danych i moĔliwoĈè kilkukrotnej iteracji, obiekt moĔe oferowaè dodatkowe metody: x rewind() — ustawia wskaĒnik na poczñtek kolejki; x current() — zwraca aktualny element, bo nie moĔna tego uczyniè za pomocñ next() bez jednoczesnej zmiany wskaĒnika. 144 _ Rozdziaĥ 7. Wzorce projektowe Implementacja tych dodatkowych metod nie sprawi Ĕadnych trudnoĈci. var agg = (function () { // [jak wyĪej...] return { // [jak wyĪej...] rewind: function () { index = 0; }, current: function () { return data[index]; } }; }()); Oto dodatkowy test iteratora: // pĊtla wyĞwietla wartoĞci 1, 3 i 5 while (agg.hasNext()) { console.log(agg.next()); } // powrót na początek agg.rewind(); console.log(agg.current()); // 1 W konsoli pojawiñ siö nastöpujñce wartoĈci: 1, 3 i 5 (z pötli), a na koþcu ponownie 1 (po przejĈciu na poczñtek kolejki). Dekorator We wzorcu dekoratora dodatkowñ funkcjonalnoĈè moĔna dodawaè do obiektu dynamicznie w trakcie dziaäania programu. W przypadku korzystania ze statycznych i niezmiennych klas jest to faktycznie duĔe wyzwanie. W jözyku JavaScript obiekty moĔna modyfikowaè, wiöc dodanie do nich nowej funkcjonalnoĈci nie stanowi wielkiego problemu. Dodatkowñ cechñ wzorca dekoratora jest äatwoĈè dostosowania i konfiguracji jego oczekiwa- nego zachowania. Zaczyna siö od prostego obiektu z podstawowñ funkcjonalnoĈciñ. Nastöp- nie wybiera siö kilka z zestawu dostöpnych dekoratorów, po czym rozszerza siö nimi pod- stawowy obiekt. Czasem istotna jest kolejnoĈè tego rozszerzania. Sposób uŜycia Przyjrzyjmy siö sposobom uĔycia tego wzorca. PrzypuĈèmy, Ĕe opracowujemy aplikacjö, która coĈ sprzedaje. KaĔda nowa sprzedaĔ to nowy obiekt sale. Obiekt zna cenö produktu i potrafi jñ zwróciè po wywoäaniu metody sale.getPrice(). W zaleĔnoĈci od aktualnych warunków moĔna zaczñè „dekorowaè” obiekt dodatkowñ funkcjonalnoĈciñ. WyobraĒmy sobie, Ĕe jako amerykaþski sklep sprzedajemy produkt klientowi z kanadyjskiej prowincji Québec. W takiej sytuacji klient musi zapäaciè podatek federalny i dodatkowo podatek lokalny. We wzorcu dekoratora bödziemy wiöc „dekorowali” obiekt dekoratorem podatku federalnego i dekora- torem podatku lokalnego. Po wyliczeniu ceny koþcowej moĔna równieĔ dodaè dekorator do jej formatowania. Scenariusz byäby nastöpujñcy: Dekorator _ 145 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 kupujñcy moĔe mieszkaè w prowincji, która nie stosuje podatku lokal- nego, i dodatkowo moĔemy 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 zauwaĔyè, Ĕe jest to wygodny i elastyczny sposób dodawania lub modyfikowania funkcjonalnoĈci utworzonych juĔ obiektów. Czas na implementacjö wzorca. Implementacja Jednym ze sposobów implementacji wzorca dekoratora jest utworzenie dekoratorów jako obiektów zawierajñcych metody do nadpisania. KaĔdy dekorator dziedziczy wówczas tak naprawdö po obiekcie rozszerzonym przez poprzedni dekorator. KaĔda dekorowana metoda wywoäuje swojñ poprzedniczkö za pomocñ uber (odziedziczony obiekt), pobiera wartoĈè i przetwarza jñ, dodajñc coĈ nowego. Efekt jest taki, Ĕe wywoäanie metody sale.getPrice() z pierwszego z przedstawionych przykäadów powoduje tak naprawdö wywoäanie metody dekoratora money (patrz rysunek 7.1). PoniewaĔ jednak kaĔdy dekorator wywoäuje najpierw odpowiadajñcñ mu metodö ze swego poprzednika, getPrice() z money wywoäuje getPrice() z quebec, a ta metodö getPrice() z fedtax i tak dalej. ãaþcuch moĔe byè däuĔszy, ale koþczy siö oryginalnñ metodñ getPrice() zaimplementowanñ przez konstruktor Sale(). Rysunek 7.1. Implementacja wzorca dekoratora 146 _ Rozdziaĥ 7. Wzorce projektowe 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 wäaĈciwoĈci konstruktora: Sale.decorators = {}; Przyjrzyjmy siö przykäadowemu dekoratorowi. To obiekt implementujñcy zmodyfikowanñ wersjö metody getPrice(). Metoda najpierw pobiera zwróconñ przez metodö przodka wartoĈè, a nastöpnie jñ modyfikuje. Sale.decorators.fedtax = { getPrice: function () { var price = this.uber.getPrice(); price += price * 5 / 100; return price; } }; W podobny sposób moĔna zaimplementowaè dowolnñ liczbö innych dekoratorów. Mogñ one stanowiè rozszerzenie podstawowej funkcjonalnoĈci Sale(), czyli dziaäaè jak dodatki. Co wiöcej, nic nie stoi na przeszkodzie, by znajdowaäy siö w dodatkowych plikach i byäy implementowane przez innych, niezaleĔnych 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 uĔycia jest nastöpujñcy: 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 rozdziaäu. Dodatkowo ustawmy wäaĈciwoĈè uber obiektu newobj, by potomek miaä dostöp do przodka. Nastöpnie niech kod kopiuje wszystkie wäa- ĈciwoĈci z dekoratora do nowego obiektu i zwraca newobj jako wynik caäej operacji, co spo- woduje, Ĕe stanie siö on nowym obiektem sale. Dekorator _ 147 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 wykorzystujéca listý Przeanalizujmy innñ implementacjö, która korzysta z dynamicznej natury jözyka JavaScript i w ogóle nie stosuje dziedziczenia. Dodatkowo, zamiast wymuszaè na kaĔdej metodzie de- korujñcej, by wywoäywaäa swojñ poprzedniczkö, przekazujemy tu wynik poprzedniej metody jako parametr nastöpnej. Taka implementacja znaczñco uäatwia wycofanie udekorowania, czyli usuniöcie jednego z elementów z listy dekoratorów. Sposób uĔycia nowej implementacji bödzie prostszy, bo nie wymaga ona przypisywania wartoĈci 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 wäasnñ wäaĈciwoĈè. function Sale(price) { this.price = (price 0) || 100; this.decorators_list = []; } Dostöpne dekoratory sñ ponownie implementowane jako wäaĈciwoĈci Sale.decorators. Sñ prostsze, bo nie muszñ juĔ wywoäywaè poprzedniej wersji metody getPrice(), by uzy- skaè wartoĈè poĈredniñ. 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; } }; 148 _ Rozdziaĥ 7. Wzorce projektowe Sale.decorators.money = { getPrice: function (price) { return USD + price.toFixed(2); } }; Interesujñce konstrukcje pojawiajñ siö w metodach decorate() i getPrice() oryginalnego obiektu. W poprzedniej implementacji metoda decorate() byäa w miarö zäoĔona, a getPrice() niezwykle prosta. W nowej jest dokäadnie odwrotnie — decorate() po prostu dodaje nowy element do listy, a getPrice() wykonuje caäñ istotnñ pracö. Pracñ tñ jest przejĈcie przez listö wszystkich dodanych dekoratorów i wywoäanie dla kaĔdego z nich metody getPrice() z po- przedniñ wartoĈciñ 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 dekorujñce. Caäñ rzeczywistñ pracö wykonuje metoda, która „zgadza” siö na dekoracjö. W tej prostej implementacji dekoracjö dopuszcza jedynie metoda getPrice(). JeĈli dekoracja mia- äaby dotyczyè wiökszej liczby metod, kaĔda z nich musiaäaby przejĈè przez listö dekoratorów i wywoäaè odpowiednie metody. OczywiĈcie taki kod stosunkowo äatwo jest umieĈciè w osobnej metodzie pomocniczej i uogólniè. UmoĔliwiaäby on dodanie dekorowalnoĈci do dowolnej metody. Co wiöcej, w takiej implementacji wäaĈciwoĈè decorators_list byäaby obiektem z wäaĈciwoĈciami o nazwach metod i z tablicami dekorowanych obiektów jako wartoĈciami. Strategia Wzorzec strategii umoĔliwia wybór odpowiedniego algorytmu na etapie dziaäania aplikacji. UĔytkownicy kodu mogñ stosowaè ten sam interfejs zewnötrzny, ale wybieraè spoĈród kilku dostöpnych algorytmów, by lepiej dopasowaè implementacjö do aktualnego kontekstu. Przykäadem wzorca strategii moĔe byè rozwiñzywanie problemu walidacji formularzy. MoĔna utworzyè jeden obiekt sprawdzania z metodñ validate(). Metoda zostanie wywoäana nie- zaleĔnie od rodzaju formularza i zawsze zwróci ten sam wynik — listö danych, które nie sñ poprawne, wraz z komunikatami o bäödach. W zaleĔnoĈci od sprawdzanych danych i typu formularza uĔytkownik kodu moĔe wybraè róĔne rodzaje sprawdzeþ. Walidator wybiera najlepszñ strategiö wykonania zadania i dele- guje konkretne czynnoĈci sprawdzeþ do odpowiednich algorytmów. Strategia _ 149 Przykĥad walidacji danych PrzypuĈèmy, Ĕe mamy do czynienia z nastöpujñcym zestawem danych pochodzñcym naj- prawdopodobniej z formularza i Ĕe chcemy go sprawdziè pod kñtem poprawnoĈci: var data = { first_name: Super , last_name: Man , age: unknown , username: o_O }; Aby walidator znaä najlepszñ strategiö do zastosowania w tym konkretnym przykäadzie, trzeba najpierw go skonfigurowaè, okreĈlajñc zestaw reguä i wartoĈci uznawanych za prawidäowe. PrzypuĈèmy, Ĕe nie wymagamy podania nazwiska i zaakceptujemy dowolnñ wartoĈè imienia, ale wymagamy podania wieku jako liczby i nazwy uĔytkownika, która skäada siö tylko z liczb i liter bez znaków specjalnych. Konfiguracja mogäaby wyglñdaè nastöpujñco: validator.config = { first_name: isNonEmpty , age: isNumber , username: isAlphaNum }; Po skonfigurowaniu obiektu validator jest on gotowy do przyjöcia danych. Wywoäujemy jego metodö validate() i wyĈwietlamy bäödy walidacji w konsoli. validator.validate(data); if (validator.hasErrors()) { console.log(validator.messages.join( )); } Efektem wykonania kodu mógäby byè nastöpujñcy komunikat: Niepoprawna wartoŁð *age*; wartoŁð musi byð liczbî, na przykĪad 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 bäödzie. // sprawdzenie, czy podano jakąĞ wartoĞü validator.types.isNonEmpty = { validate: function (value) { return value !== ; }, instructions: wartoŁð nie moše byð pusta }; // sprawdzenie, czy wartoĞü jest liczbą validator.types.isNumber = { validate: function (value) { return !isNaN(value); }, instructions: wartoŁð musi byð liczbî, na przykĪad 1, 3.14 lub 2010 }; // sprawdzenie, czy wartoĞü zawiera jedynie litery i cyfry validator.types.isAlphaNum = { validate: function (value) { 150 _ Rozdziaĥ 7. Wzorce projektowe return !/[^a-z0-9]/i.test(value); }, instructions: wartoŁð musi zawierað jedynie litery i cyfry bez šadnych znaków ´specjalnych }; NajwyĔszy czas na obiekt validator: var validator = { // wszystkie dostĊpne sprawdzenia types: {}, // komunikaty o báĊdach // 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; // usuniĊcie 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 obsĪugi 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; } }; Strategia _ 151 Obiekt validator jest uniwersalny i bödzie dziaäaä prawidäowo dla róĔnych rodzajów spraw- dzeþ. Jednym z usprawnieþ mogäoby byè dodanie kilku nowych testów. Po wykonaniu kilku róĔnych formularzy z walidacjñ Twoja lista dostöpnych sprawdzeþ z pewnoĈciñ siö wydäuĔy. KaĔdy kolejny formularz bödzie 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þ. Stosujñc to podejĈcie, uzyskuje siö znacznie wiöcej metod niĔ w przypadku tworzenia supermetod z wie- loma parametrami. W wiökszoĈci sytuacji dwie lub wiöcej metod wykonuje siö jednoczeĈnie. Warto wtedy utworzyè jeszcze jednñ metodö, która stanowi otoczkö dla takich poäñczeþ. W trakcie obsäugi zdarzeþ przeglñdarki bardzo czösto korzysta siö z nastöpujñcych metod: x stopPropagation() — zapobiega wykonywaniu obsäugi zdarzenia w wözäach nadrzödnych; x preventDefault() — zapobiega wykonaniu przez przeglñdarkö domyĈlnej akcji dla zda- rzenia (na przykäad klikniöcia äñcza lub wysäania formularza). To dwie osobne metody wykonujñce odmienne zadania, wiöc nie stanowiñ jednej caäoĈci, ale z dru- giej strony w zdecydowanej wiökszoĈci sytuacji sñ one wykonywane jednoczeĈnie. Zamiast wiöc powielaè wywoäania obu metod w caäej aplikacji, moĔna 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 pomiödzy przeglñdarkami internetowymi. Nic nie stoi na przeszkodzie, by rozbudowaè po- przedni przykäad o inny sposób obsäugi anulowania zdarzeþ przez przeglñdarkö 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; } } // ... }; 152 _ Rozdziaĥ 7. Wzorce projektowe Wzorzec fasady bywa pomocny w przypadku zmiany interfejsów zwiñzanej na przykäad z refaktoryzacjñ. Gdy chce siö zamieniè obiekt na innñ implementacjö, najczöĈciej moĔe to zajñè sporo czasu (jeĈli jest on naprawdö rozbudowany). ZaäóĔmy teĔ, Ĕe juĔ powstaje kod dla nowego interfejsu. W takiej sytuacji moĔna 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. Poļrednik We wzorcu projektowym poĈrednika jeden obiekt stanowi interfejs dla innego obiektu. RóĔni siö to od wzorca fasady, w którym po prostu istniejñ pewne metody dodatkowe äñczñce w sobie wywoäania kilku innych metod. PoĈrednik znajduje siö miödzy uĔytkownikiem a obiektem i broni dostöpu do niego. Choè wzorzec wyglñda jak dodatkowy narzut, w rzeczywistoĈci czösto säuĔy do poprawy wydajnoĈci. PoĈrednik staje siö straĔnikiem rzeczywistego obiektu i stara siö, by ten wykonaä jak najmniej pracy. Jednym z przykäadów zastosowania poĈrednika 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 uĔyje. PoĈrednik moĔe wtedy stanowiè interfejs dla rzeczywistego obiektu. Otrzymuje polecenie inicjalizacji, ale nie przekazuje go dalej aĔ do momentu, gdy rzeczywisty obiekt naprawdö zostanie uĔyty. Rysunek 7.2 ilustruje sytuacjö, w której klient wysyäa polecenie inicjalizujñce, a poĈrednik odpowiada, Ĕe wszystko jest w porzñdku, choè tak naprawdö nie przekazuje polecenia dalej. Czeka z inicjalizacjñ wäaĈciwego obiektu do czasu, gdy klient rzeczywiĈcie bödzie wykony- waä na nim pracö — wówczas przekazuje obydwa komunikaty. Rysunek 7.2. Komunikacja miödzy klientem i rzeczywistym obiektem z wykorzystaniem poĈrednika Przykĥad Wzorzec poĈrednika bywa przydatny, gdy rzeczywisty obiekt docelowy wykonuje kosztow- ne zadanie. W aplikacjach internetowych jednñ z kosztownych sytuacji jest Ĕñdanie sieciowe, wiöc w miarö moĔliwoĈci warto zebraè kilka operacji i wykonaè je jednym Ĕñdaniem. Prze- ĈledĒmy praktyczne zastosowanie wzorca wäaĈnie w takiej sytuacji. Poļrednik _ 153 Aplikacja wideo ZaäóĔmy istnienie prostej aplikacji odtwarzajñcej materiaä wideo wybranego artysty (patrz rysunek 7.3). W zasadzie moĔesz nawet przetestowaè kod, wpisujñc w przeglñdarce 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 uĔytkownik kliknie tytuä, obszar poniĔej rozszerzy siö, by przedstawiè dodatkowe informacje i umoĔliwiè odtworzenie filmu. Szcze- góäy dotyczñce materiaäów oraz adres URL treĈci wideo nie stanowiñ czöĈci strony — sñ po- bierane poprzez osobne wywoäania serwera. Serwer przyjmuje jako parametr kilka identyfi- katorów materiaäów wideo, wiöc aplikacjö moĔna przyspieszyè, wykonujñc mniej Ĕñdaþ HTTP i pobierajñc za kaĔdym razem dane kilku filmów. Aplikacja umoĔliwia jednoczesne rozwiniöcie szczegóäów kilku (a nawet wszystkich) mate- riaäów, co stanowi doskonaäñ okazjö do poäñczenia kilku Ĕñdaþ w jedno. Bez uŜycia poļrednika 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(). 154 _ Rozdziaĥ 7. Wzorce projektowe Bez stosowania poĈrednika videos.getInfo() wywoäa http.makeRequest() dla kaĔdego materiaäu wideo. Po dodaniu poĈrednika pojawi siö nowy obiekt o nazwie proxy znajdujñcy siö miödzy videos oraz http i delegujñcy wszystkie wywoäania makeRequest(), a takĔe äñ- czñcy je ze sobñ. Najpierw pojawi siö kod, w którym nie zastosowano wzorca poĈrednika. Druga wersja, sto- sujñca obiekt poĈrednika, poprawi ogólnñ päynnoĈè dziaäania aplikacji. Kod HTML Kod HTML to po prostu zbiór äñczy. p span id= toggle-all PrzeĪîcz 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 Obsĥuga zdarzeħ Zanim pojawi siö wäaĈciwa obsäuga zdarzeþ, warto dodaè funkcjö pomocniczñ $ do pobiera- nia elementów DOM na podstawie ich identyfikatorów. var $ = function (id) { return document.getElementById(id); }; Stosujñc delegacjö zdarzeþ (wiöcej na ten temat w rozdziale 8.), moĔna obsäuĔyè wszystkie klikniöcia dotyczñce listy uporzñdkowanej 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 ) { Poļrednik _ 155 src.parentNode.innerHTML = videos.getPlayer(id); return; } src.parentNode.id = v + id; videos.getInfo(id); }; Obsäuga klikniöcia zainteresowana jest tak naprawdö dwoma sytuacjami: pierwszñ dotyczñcñ rozwiniöcia lub zamkniöcia czöĈci informacyjnej (wywoäanie getInfo()) i drugñ zwiñzanñ z odtworzeniem materiaäu wideo (gdy klikniöcie dotyczyäo obiektu z klasñ play), co oznacza, Ĕe rozwiniöcie juĔ nastñpiäo i moĔna bezpiecznie wywoäaè metodö getPlayer(). Identyfika- tory materiaäów wideo wydobywa siö z atrybutów href äñczy. Druga z funkcji obsäugujñcych klikniöcia dotyczy sytuacji, w której uĔytkownik chce przeäñ- czyè wszystkie czöĈci informacyjne. W zasadzie sprowadza siö ona do wywoäywania w pötli 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 niezbödny do odtworzenia materiaäu wideo (nie- istotny w rozwaĔaniach na temat obiektu poĈrednika). x updateList() — wywoäanie zwrotne otrzymujñce wszystkie dane z serwera i generujñce kod HTML do wykorzystania przy rozwijaniu szczegóäów filmów (w tej metodzie rów- nieĔ nie dzieje siö nic interesujñcego). x getInfo() — metoda przeäñczajñca widocznoĈè czöĈci informacyjnych i wykonu- jñca metody obiektu http przez przekazanie updateList() jako funkcji wywoäania zwrotnego. 156 _ Rozdziaĥ 7. Wzorce projektowe 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 usäugi 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 usäuga internetowa, która oferuje moĔ- liwoĈè korzystania ze skäadni przypominajñcej SQL do pobierania danych z innych usäug. W ten sposób nie trzeba poznawaè szczegóäów ich API. Gdy jednoczeĈnie przeäñczone zostanñ wszystkie materiaäy wideo, do serwera trafi szeĈè osobnych Ĕñdaþ; kaĔde bödzie podobne do nastöpujñcego Ĕñdania YQL: select * from music.video.id where ids IN ( 2158073 ) Obiekt proxy Zaprezentowany wczeĈniej kod dziaäa prawidäowo, ale moĔna go zoptymalizowaè. Na scenö wkracza obiekt proxy, który przejmuje komunikacjö miödzy http i videos. Obiekt stara siö poäñczyè ze sobñ kilka Ĕñdaþ, czekajñc na ich zebranie 50 ms. Obiekt videos nie wywoäuje Poļrednik _ 157 usäugi HTTP bezpoĈrednio, ale przez poĈrednika. Ten czeka krótkñ chwilö z wysäaniem Ĕñ- dania. JeĈli wywoäania z videos bödñ przychodziäy w odstöpach krótszych niĔ 50 ms, zostanñ poäñczone w jedno Ĕñdanie. Takie opóĒnienie nie jest szczególnie widoczne, ale pomaga zna- czñco przyspieszyè dziaäanie aplikacji w przypadku jednoczesnego odsäaniania wiöcej niĔ jednego materiaäu wideo. Co wiöcej, jest równieĔ przyjazne dla serwera, który nie musi ob- säugiwaè sporej liczby Ĕñdaþ. Zapytanie YQL dla dwóch materiaäów wideo moĔe mieè postaè: select * from music.video.id where ids IN ( 2158073 , 123456 ) W istniejñcym kodzie zachodzi tylko jedna zmiana: metoda videos.getInfo() wywoäuje metodö proxy.makeRequest() zamiast metody http.makeRequest(). proxy.makeRequest(id, videos.updateList, videos); Obiekt poĈrednika korzysta z kolejki, w której gromadzi identyfikatory materiaäów wideo przeka- zane w ostatnich 50 ms. Nastöpnie przekazuje wszystkie identyfikatory, wywoäujñc metodö obiektu http i przekazujñc wäasnñ funkcjö wywoäania zwrotnego, poniewaĔ videos.updateList() potrafi przetworzyè tylko pojedynczy rekord danych. Oto kod obiektu poĈredniczñcego 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; } 158 _ Rozdziaĥ 7. Wzorce projektowe // 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 poĈrednika umoĔliwiäo poäñczenie 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 poĈrednika) i z jednym poäñczonym Ĕñdaniem (po uĔyciu poĈrednika). Rysunek 7.4. Trzy osobne Ĕñdania do serwera Rysunek 7.5. Wykorzystanie poĈrednika do zmniejszenia liczby Ĕñdaþ wysyäanych do serwera Poļrednik jako pamiýë podrýczna W prezentowanym przykäadzie obiekt videos Ĕñdajñcy danych jest na tyle inteligentny, Ĕe nie Ĕñda tych samych informacji dwukrotnie. Nie zawsze jednak musi tak byè. PoĈrednik moĔe pójĈè o krok dalej i chroniè rzeczywisty obiekt http przed powielaniem tych samych Ĕñdaþ, zapamiötujñc je w nowej wäaĈciwoĈci cache (patrz rysunek 7.6). Gdyby obiekt videos ponownie poprosiä o informacje o tym samym materiale (ten sam identyfikator), poĈrednik wydobyäby dane z pamiöci podröcznej i uniknñä komunikacji z serwerem. Rysunek 7.6. Pamiöè podröczna w obiekcie poĈrednika Poļrednik _ 159 Mediator Aplikacje — duĔe czy maäe — skäadajñ siö z wielu obiektów. Obiekty muszñ siö ze sobñ ko- munikowaè w sposób, który nie uczyni przyszäej konserwacji kodu prawdziwñ drogñ przez mökö i umoĔliwi 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 duĔo i komunikujñ siö bezpoĈrednio (wywoäujñ siö wzajemnie i modyfikujñ wäaĈciwoĈci), powstaje miödzy nimi niepoĔñdany Ĉcisäy zwiñzek. JeĈli obiekty sñ ze sobñ powiñzane zbyt mocno, nieäatwo zmie- niè jeden z nich bez modyfikacji pozostaäych. Wtedy nawet najprostsza zmiana w aplikacji nie jest däuĔej trywialna i bardzo trudno oszacowaè, ile tak naprawdö czasu trzeba bödzie na niñ poĈwiöciè. Wzorzec mediatora ma za zadanie promowaè luĒne powiñzania obiektów i wspomóc przy- szäñ konserwacjö kodu (patrz rysunek 7.7). W tym wzorcu niezaleĔne obiekty (koledzy) nie komunikujñ siö ze sobñ bezpoĈrednio, 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 Przykĥad mediatora PrzeĈledĒmy przykäad uĔycia wzorca mediatora. Aplikacja bödzie grñ, w której dwóch gra- czy przez póä minuty stara siö jak najczöĈciej klikaè w przycisk. Pierwszy gracz naciska kla- wisz nr 1, a drugi klawisz 0 (spory odstöp miödzy klawiszami zapewnia, Ĕe nie pobijñ siö o klawiaturö). Tablica wyników pokazuje aktualny stan rywalizacji. Obiektami uczestniczñcymi w wymianie informacji sñ: x pierwszy gracz, x drugi gracz, x tablica, x mediator. 160 _ Rozdziaĥ 7. Wzorce projektowe Mediator wie o wszystkich obiektach. Komunikuje siö z urzñdzeniem wejĈciowym (klawiaturñ), obsäuguje naciĈniöcia klawiszy, okreĈla, który gracz jest aktywny, i informuje o zmianach wyników (patrz rysunek 7.8). Gracz jedynie gra (czyli aktualizuje swój wäasny wynik) i in- formuje mediator o tym zdarzeniu. Mediator informuje tablicö o zmianie wyniku, a ta aktu- alizuje wyĈwietlanñ wartoĈè. Rysunek 7.8. Uczestnicy w grze na szybkoĈè naciskania klawiszy Poza mediatorem Ĕaden inny obiekt nie wie nic o pozostaäych. Dziöki temu bardzo äatwo zaktualizowaè grö, na przykäad dodaè nowego gracza lub zmieniè tablicö wyników na wersjö wyĈwietlajñcñ pozostaäy czas. Peäna wersja gry wraz z kodem Ēródäowym jest dostöpna pod adresem http://www.jspatterns.com/ book/7/mediator.html. Obiekty graczy sñ tworzone przy uĔyciu konstruktora Player() i zawierajñ wäasne wäaĈci- woĈci points i name. Metoda play() z prototypu zwiöksza 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() wywoäywanñ przez mediator po zdobyciu punktu przez jednego z graczy. Tablica nie wie nic o graczach i nie przechowuje wyniku — po prostu wyĈwietla informacje przekazane przez mediator. var scoreboard = { // aktualizowany element HTML element: document.getElementById( results ), // aktualizacja wyĞwietlacza update: function (score) { var i, msg = ; for (i in score) { if (score.hasOwnProperty(i)) { Mediator _ 161 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þ dziöki umieszczeniu ich we wäaĈciwoĈci players. Metoda played() zostaje wywoäana przez kaĔdego z graczy po wykonaniu akcji. Aktualizuje ona wynik (score) i przesyäa go do tablicy (scoreboard). Ostatnia metoda, keypress(), obsäuguje zdarzenia klawiatury, okreĈla, 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( GoŁcie ); }, // ktoĞ zagraá, uaktualnij wynik played: function () { var players = this.players, score = { Gospodarze : players.home.points, GoŁcie : players.guest.points }; scoreboard.update(score); }, // obsáuga interakcji z uĪytkownikiem 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 zakoþczenie gry. // start! mediator.setup(); window.onkeypress = mediator.keypress; // gra koĔczy siĊ po 30 sekundach setTimeout(function () { window.onkeypress = null; alert( Koniec gry! ); }, 30000); 162 _ Rozdziaĥ 7. Wzorce projektowe Obserwator Wzorzec obserwatora jest niezwykle czösto wykorzystywany w programowaniu po stronie klienta w jözyku JavaScript. Wszystkie zdarzenia przeglñdarki (poruszenie myszñ, naciĈniöcie klawisza itp.) to przykäady jego uĔycia. Innñ czösto pojawiajñcñ siö nazwñ tego wzorca sñ zdarzenia wäasne, czyli zdarzenia tworzone przez programistö, a nie przeglñdarkö. Jeszcze inna nazwa to wzorzec subskrybenta-dostawcy. Gäównym celem uĔywania wzorca jest promowanie luĒnego powiñzania elementów. Zamiast sytuacji, w której jeden obiekt wywoäuje metodö drugiego, mamy sytuacjö, w której drugi z obiektów zgäasza chöè otrzymywania powiadomieþ o zmianie w pierwszym obiekcie. Sub- skrybenta nazywa siö czösto obserwatorem, a obiekt obserwowany obiektem publikujñcym lub Ēródäem. Obiekt publikujñcy wywoäuje subskrybentów po zajĈciu istotnego zdarzenia i bardzo czösto przekazuje informacjö o nim w postaci obiektu zdarzenia. Pierwszy przykĥad — subskrypcja magazynu Aby dowiedzieè siö, jak zaimplementowaè wzorzec, posäuĔmy siö konkretnym przykäadem. PrzypuĈèmy, Ĕe mamy wydawcö paper, który publikuje gazetö codziennñ i miesiöcznik. Sub- skrybent joe zostanie powiadomiony o wydaniu nowego periodyku. Obiekt paper musi zawieraè wäaĈciwoĈè subscribers, która jest tablicñ przechowujñcñ wszystkich subskrybentów. Zgäoszenie siö do subskrypcji polega jedynie na dodaniu nowego wpisu do tablicy. Gdy zajdzie istotne zdarzenie, obiekt paper przejdzie w pötli przez wszyst- kich subskrybentów, by ich o nim powiadomiè. Notyfikacja polega na wywoäaniu metody obiektu subskrybenta. Oznacza to, Ĕe w momencie zgäoszenia chöci otrzymywania powia- domieþ subskrybent musi przekazaè obiektowi paper jednñ ze swoich metod w wywoäaniu metody subscribe(). Obiekt paper moĔe dodatkowo umoĔliwiè anulowanie subskrypcji, czyli usuniöcie wpisu z tablicy subskrybentów. Ostatniñ istotnñ metodñ obiektu paper jest publish(), która wy- woäuje metody subskrybentów. Podsumowujñc, obiekt publikujñcy musi zawieraè nastöpujñce skäadowe: x subscribers — tablica; x subscribe() — dodaje wpis do tablicy; x unsubscribe() — usuwa wpis z tablicy; x publish() — przechodzi w pötli przez subs
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

JavaScript. Wzorce
Autor:

Opinie na temat publikacji:


Inne popularne pozycje z tej kategorii:


Czytaj również:


Prowadzisz stronę lub blog? Wstaw link do fragmentu tej książki i współpracuj z Cyfroteką: