Wypróbuj

Nozbe - get things done

i zwiększ swoją produktywność

Wzorzec Strategia w JavaScript

0

Postem tym chciałbym rozpocząć mini cykl o zastosowaniu wzorców projektowych w języku JavaScript –  wbrew pozorom, w tym języku również można je stosować! Na pierwszy ogień przedstawię dziś wzorzec Strategia w JavaScript. Myślę, że większość z nas, stosowała i stosuje ten wzorzec na co dzień (czasem nawet nieświadomie), jednak dla przypomnienia (za wikipedią):

Wzorzec strategii definiuje rodzinę algorytmów, z których każdy zdefiniowany jest w osobnej klasie implementującej wspólny interfejs – dzięki temu możliwe jest wymienne stosowanie każdego z tych algorytmów, nie będąc zależnymi od klas klienckich.

Przykład – HTML

To tyle tytułem wstępu – zakładam, że implementacja tego wzorca w języku takim jak np. C# jest znana, chciałbym się więc skupić na tym jak skorzystać z niego JavaScript. Dla osób, które nie miały dotąd styczności z OOP w JavaScript, proponuje wcześniejsze zapoznanie się z tą tematyką. Można zacząć na przykład od stron mozilli dla developerów. Przejdźmy zatem do przykładu – załóżmy taki oto kod html:

</pre>
<fieldset><label for="error">Błąd</label>
  <input type="radio" name="selection" value="error" />

  <label for="warnirng">Ostrzeżenie</label>
  <input type="radio" name="selection" value="warning" />

  <label for="info">Informacja</label>
  <input type="radio" name="selection" value="info" /></fieldset>
<pre>
<input type="button" value="Wyswietl" />

---

Mamy tutaj trzy radiobuttony, przycisk ‚Wyświetl’ oraz paragraf. Radiobuttony odpowiadają opcjom ‚Błąd’, ‚Ostrzeżenie’ oraz ‚Informacja’ – po wybraniu jednego z nich i naciśnięciu przycisku ‚Wyświetl’, wewnątrz tag’u <p>, zamiast wartości ‚—‚ ma się pojawić odpowiednia informacja (dodatkowo w zależności czy będzie to błąd, ostrzeżenie czy informacja, tekst powinien być odpowiednio „ostylowany”).

Obsługa zadania

Do obsługi takiego zadania moglibyśmy użyć takiego kodu JavaScript:

 $(document).ready(function() {
    $('input:radio').change(function() {
        var checkedVal = $(this).val();

        switch (checkedVal) {
            case 'error':
                $('input[type="button"]').click(function() {
                    $('p').attr('class', '')
                        .addClass('error')
                        .text('Błąd');
                });
                break;
             case 'warning':
                $('input[type="button"]').click(function() {
                    $('p').attr('class', '')
                        .addClass('warning')
                        .text('Ostrzeżenie');
                });
                break;
            case 'info':
                $('input[type="button"]').click(function() {
                    $('p').attr('class', '')
                        .addClass('info')
                        .text('Informacja');
                });
                break;
        }
    });
});

Jak widać, w „on document ready” (jQuery) mamy inicjalizację obsługi zdarzenia ‚change’ dla wszystkich radiobuttonów na stronie. Funkcja obsługująca zdarzenie pobiera wartość atrybutu ‚value’ radiobuttona, który został w danym momencie kliknięty – w zależności od tej wartości, definiowana jest funkcja obsługująca zdarzenie ‚click’ przycisku (tego, który wyświetlać ma komunikat). W powyższym przykładzie, zastosowana została instrukcja warunkowa switch, a każdy z obsługiwanych przypadków jest bardzo podobny – mamy funkcję obsługującą zdarzenie ‚click’, a w niej, z pomocą metod jQuery, odpowiednie „ostylowanie” elementu <p> i nadanie odpowiedniego tekstu.

Myślę, że w takim przypadku, aż się prosi o zastosowanie wzorca strategii… W naszym przypadku, utworzymy klasę abstrakcyjną zawierającą deklarację metody show(), która odpowiedzialna będzie za odpowiednie „ostylowanie” naszego elementu <p> – odpowiednia implementacja tej metody będzie wywoływana w funkcji obsługującej zdarzenie click buttona ‚Wyświetl’.

Implementacja strategii

Zacznijmy więc przykład implementacji strategii, na początek klasa bazowa:

var Message = function() {}; // define abstract class
// declare abstract function
Message.prototype.show = function() {
    throw 'Implement abstract class!!';
};

Mamy tutaj definicję klasy Message, z konstruktorem bez parametrów (function() nie przyjmuje żadnych parametrów). Ponadto definiujemy metodę show(), która rzuca wyjątkiem jeśli próbowalibyśmy użyć jej bezpośrednio (czyli jest to metoda bardziej wirtualna niż abstrakcyjna – w JavaScript nie mamy takiego rozróżnienia, liczy się efekt jaki w ten sposób osiągamy – jeśli w klasie dziedziczącej po Message nie zaimplementujemy metody show(), rzucony zostanie wyjątek).

Klasy dziedziczące po Message

Kolejna rzecz, to implementacja poszczególnych klas strategii dziedziczących po Message. Poniżej jedna z takich klas:

var ErrorMessage = function() { /*parameterless constructor*/ };
ErrorMessage.prototype = new Message; // extend of abstract class
ErrorMessage.prototype.show = function() {
    // first algorithm implementation
    $('p').attr('class', '')
        .addClass('error')
        .text('Błąd!');
};

Na powyższym przykładzie, widać w jaki sposób realizowane jest dziedziczenie w JavaScript – w pierwszej linijce mamy deklarację klasy ErrorMessage za pomocą konstruktora bezparametrowego. W kolejnej linii informujemy, że klasa ErrorMessage dziedziczy z Message (klasa Message jest prototypem dla ErrorMessage). Kolejne linijki to już konkretna implementacja/przesłonięcie metody show(). Poniżej pozostałe strategie:

var WarningMessage = function() { /*parameterless constructor*/ };
WarningMessage.prototype = new Message; // extend of abstract class
WarningMessage.prototype.show = function() {
    // first algorithm implementation
    $('p').attr('class', '')
        .addClass('warning')
        .text('Ostrzeżenie');
};

var InfoMessage = function() { /*parameterless constructor*/ };
InfoMessage.prototype = new Message; // extend of abstract class
InfoMessage.prototype.show = function() {
    // first algorithm implementation
    $('p').attr('class', '')
        .addClass('info')
        .text('Informacja');
};

Wykorzystanie strategii w praktyce

Wzorzec strategii, w codziennym zastosowaniu bardzo często występuje w połączeniu z wzorcem fabryki – np. klasy ze statycznymi metodami zwracającymi odpowiednie implementacje algorytmu. W naszym przypadku zrobimy coś podobnego – zdefiniujemy obiekt, z właściwościami, których nazwy odpowiadać będą wartościom (atrybuty ‚value’) poszczególnych radiobuttonów, a wartości przypisanym im strategiom:

var messages = {
    error: new ErrorMessage(),
    warning: new WarningMessage(),
    info: new InfoMessage()
};

Pozostało nam już tylko wykorzystanie tak utworzonych strategii w praktyce:

$(document).ready(function() {
    $('input:radio').change(function() {
        currentMessage = messages[$(this).val()];
        $('input[type="button"]').click(currentMessage.show);
    });
});

Tak jak w przykładzie na początku artykułu, mamy obsługę zdarzenia ‚change’ radiobuttonów, z tą różnicą, że zamiast instrukcji warunkowej ‚switch’ wykorzystujemy utworzone wcześniej strategie – jak widać w linii nr 3, na podstawie wartości aktualnie zaznaczonego radiobuttona, z obiektu messages, pobierana jest odpowiednia klasa strategii. Następnie metoda show() tej klasy, wykorzystywana jest do obsługi zdarzenia ‚click’ przycisku ‚Wyświetl’.

W ten sposób uniezależniliśmy wyświetlanie komunikatów od funkcji obsługującej zdarzenie ‚click’ – jeśli chcemy dokonać zmian w sposobie wyświetlania, robimy to w odpowiednich klasach strategii. Możemy również napisać nowe strategie wyświetlania komunikatów i podmienić je – wszystkie korzyści płynące z wzorca strategia są dostępne.

Wzorzec strategia w JavaScript – podsumowanie

To w zasadzie wszystko, jak widać skorzystanie z benefitów wzorców projektowych jest możliwe również w języku JavaScript. Mam nadzieję, że teraz wzorzec strategia w JavaScript nie będzie już dla Ciebie tajemnicą… W przyszłości postaram się pokazać również implementację innych wzorców, znanych nam z typowych języków obiektowych.

W pełni działający opisany powyżej dostępny jest do przetestowania w jsfiddle.

CHCESZ DARMOWEGO E-BOOKA?

Jeśli chcesz otrzymać mojego e-booka: Rozmowa Kwalifikacyjna - pytania z podstaw JavaScript zostaw mi swój e-mail:

Oprócz tego co poniedziałek dostaniesz maila z listą moich wpisów z poprzedniego tygodnia!