Dekorator (*Decorator Pattern*) ------------------------------------- Omawiając programowanie w języku Python, pojęcie dekoratora jest dwuznaczne. Może bowiem oznaczać: - dekoratory języka Python, a więc pewne **funkcje przyjmujące jako argumeny inne funkcje**. Z takich dekoratorów korzystamy poprzedzając je ze znakiem ``@``. Znanym Ci przykładem dekoratora, predefiniowanym w języku Python jest ``@property`` - wzorzec projektowy dekorator, który można zaimplementować w każdym obiektowym języku programowania. Dekoratory w języku Python ^^^^^^^^^^^^^^^^^^^^^^^^^^ Dekorator to w zasadzie funkcja, której argumentem jest inna funkcja, która .... zwraca jeszcze inną funkcję. Jako przykład niech posłuży funkcja, która zamienia w tekście małe litery na wielkie. W pierwszym, bardzo prostym przykładzie, funkcja dekorowana ``greetings()`` jest bezargumentowa i zwraca napis ``Hello!``. Dekoratorem jest funkcja ``capitalize(func)``, która: - przyjmuje jako argument dowolną funkcję (argument ``func``) - deklaruje i zwraca funkcję dekorującą ``upercase_decorator()`` - jako zmienną! - funkcja dekorująca wywołuje ``func``; zakłada że wynik jest napisem i zamienia w nim małe litery na wielkie - funkcja ``upercase_decorator()`` zwraca napis (wielkimi literami), natomiast sam dekorator ``capitalize(func)`` zwraca **funkcję dekorującą** .. raw:: html
Sam moment dekorowania funkcji ``greetings()`` odbywa się w linii 12. Znajduąca się tam instrukcja ``uppercase_greetings = capitalize(greetings)`` równoważna jest napisaniu ``@capitalize`` nad definicją funkcji ``greetings()``. Po drobnych zmianach nasz dekorator działa też dla funkcji z argumentami: .. raw:: html W tym przypadku linia 12 zastąpiona została instrukcją ``@capitalize``, którą dodano w linii 8. Poniższy przykład pokazuje, że dekoratory można zagnieżdżać - w liniach 14 i 15 wpisano dwie dekoracje, które są równoważne instrukcji ``intro_uppercase_greetings = intro(capitalize(greetings))`` : .. raw:: html Powyżej opisałem tylko część możliwości dekoratorów, które mogą np. przyjmować swoje własne argumenty. .. note:: Dekoratory w Pythonie umożliwiają łatwą modyfikację działania programu. Załóżmy że mając funkcję ``greetings()`` tworzysz **nową funkcję** ``uppercase_greetings()``. Napisanie nowej funkcji, która zamienia małe litery na wielkie, jest znacznie mniej skomplikowana, niż tworzenie dekoratora. Ale chcąc zmienić działanie programu musisz w całym kodzie zamieniać wywołanie ``greetings()`` na ``uppercase_greetings()`` albo na odwrót. Stosując dekoratory musisz tylko zakomentować lub odkomentować jedną linijkę (np. linię 15 w powyższym programie) Wzorzec dekorator ^^^^^^^^^^^^^^^^^^^^^^^^^^ Dekoratory w Pythonie znacznie ułatwiają dostosowaywanie działania programu do chwilowych potrzeb. Mają jednak poważną wadę: **nie są dynamiczne**. Dekorator musi być dodany *w czasie pisania programu*, a nie w czasie jego uruchomienia. Dodatkowo, udekorowana funkcja ``greetings()`` pozostanie udekorowana na zawsze, tzn każde jej wywołanie będzie w wariancie *uppercase*. Aby dynamicznie modyfikować działanie kodu, potrzebujesz **wzorca projektowego dekorator**. Wzorzec dekorator omówimy na przykładzie okienek aplikacji. Takie okienko może mieć: - belkę tytułową - guziki *min* - *max* - menu - pasek przewijania: poziomy, pionowy lub obydwa - guzik np. "OK" Może, ale nie musi. Generalnie programista, tworząc interfejs graficzny aplikacji, powinien być przygotowany na wszystkie mozliwości: okno z belką ale bez menu, okno z menu ale bez przewijania itp. Frontalny atak na ten problem - napisanie oddzielnej klasy na każdy typ okienka - jest skazany na porażkę. Poszczególne klasy trzeba tworzyć dynamicznie. Dynamiczna modyfikacja zachowania obiektu możliwa jest jedynie przez zawieranie. Wzorzec dekorator wymaga zdefinowania klasy dekorowanej, oraz klas - dekoratorów, które odpowiedzialne są za wprowadzanie modyfikacji. W powyższych przykładach elementem dekorowanym była funkcja ``name_yourself()``, dekoratorami zaś ``capitalize()`` oraz ``intro()``. W tym przypadku klasą dekorowaną będzie okno w wersji podstawowej - absolutne minimum, bez menu pasków i bez ikonek. Dodatki będą dekoracjami. Zacznijmy od interfesów: ``AbstractWindow`` definiuje funkcjonalność, jaką udostępniać musi każde okienko naszej aplikacji; klasa ``AbstractDecorator`` zaś definiuje dekoracje. Zauważ, że: - ``AbstractDecorator`` dziedziczy po ``AbstractWindow`` - ma to sens, bo okno z paskiem przewijania to dalej jest okno - konstruktor ``AbstractDecorator`` przyjmuje i przechowuje obiekt klasy ``AbstractWindow``. - interfejs ``AbstractWindow`` zakłada istnienie pola ``x``, ``y`` itd; dekorator sam z siebie nie wie, jakie jest położenie okna - odczytuje te wartości z obiektu, który przechowuje. - zauważ też, że metoda ``x()`` klasy ``AbstractWindow`` jest jednocześnie dekorowana ``@property`` oraz ``@abstractmethod`` .. literalinclude:: programy/decorated_windows.py :language: python :linenos: :lineno-start: 4 :lines: 4-28,52-70 Następnie tworzymy minimalne okienko, które później będziemy dekorować: .. literalinclude:: programy/decorated_windows.py :language: python :linenos: :lines: 29-49 Metoda ``draw()`` rysuje najprostsze możliwe okienko, czyli pusty prostokąt. Nastepnie tworzymy dekoratory; każdy z nich wywołuje ``draw()`` z obiektu, który sam zawiera. Oczywiście kolejność rysowania jest istotna: dekorator dodający cień najpierw rysuje cień a potem okienko, dekorator dodający menu najpierw rysuje okienko a potem dopiero menu. .. literalinclude:: programy/decorated_windows.py :language: python :linenos: :lines: 73-137 Znajdująca się na końcu powyższego kodu funkcja ``build_window()`` tworzy okienko o zadanych dekoracjach. Poniżej możesz się sam przekonać, że działanie programu jest dynamiczne. **Kliknij na białym polu poniżej**, aby utworzyć okienko. Wygląd okna zależy od tego, jakie dekoracje wybierzesz w menu po prawej. .. raw:: html :file: programy/decorated_windows.html Wzorzec dekorator jest podobny do wzorca strategia. Generalnie różnica między nimi jest taka, że strategia służy do dynamicznego zmieniania działania obiektu, podczas gdy dekorator służy do tworzenia różnorodnych wariantów danej klasy.