W poprzednim wpisie odnośnie funkcji anonimowych poznaliśmy sposoby ich tworzenia. Dowiedzieliśmy się jak przekształcić funkcje anonimowe w domknięcia. Teraz czas na rozwinięcie tego tematu oraz zaprezentowanie do czego tak naprawdę mogą nam się przydać. Dodatkowo postaram się pokazać do czego używają ich inni programiści w swoich projektach oraz spróbujemy wymyślić kilka mniej lub bardziej abstrakcyjnych własnych zastosowań.
Zaczynajmy !
Przykłady użyć funkcji anonimowy / domknięć
Prosty system szablonów
Ok skoro wiemy już, że za pomocą funkcji anonimowych możemy dobrać się do wnętrza naszego przykładowego obiektu, to wygenerowanie wynikowego html’a nie powinno stanowić żadnego problemu, spróbujmy zatem.
Zacznijmy od zdefiniowania sobie przykładowej templatki
<h1>To jest treść templatki</h1> <h3>A to jest zawartość przesłanego obiektu: <span><?php echo $this->text ?></span></h3>
Teraz już tylko musimy zaprojektować prosty system szablonowy, może on przykładowo wyglądać o właśnie tak
<?php class TemplateRender { public function render($html,$object) { $closure = function() use($html){ ob_start(); include($html); return ob_end_flush(); }; return $closure->call($object); } }
Potrzebujemy teraz tylko przykładowego obiektu z danymi
class Text { private $text = 'hello world'; }
I gotowe, nasz prosty system szablonowy jest gotowy do użycia, sprawdź sam
<?php class TemplateRender { public function render($html,$object) { $closure = function() use($html){ ob_start(); include($html); return ob_end_flush(); }; return $closure->call($object); } } class Text { private $text = 'hello world'; } $template = new TemplateRender(); $template->render('exampleTemplate.php', new Text());
Jakie to proste i przyjemne, oczywiście jest to bardzo prosty przykład, lecz myślę, że bardzo mocno obrazuje przydatność i możliwości jakie dają nam domknięcia. Przejdźmy teraz do następnego przykładu
Wzorzec projektowy „Dekorator” (Decorator Pattern)
Za pomocą funkcji anonimowych mamy możliwość nie tylko dekorowania innych funkcji ale także całych obiektów. Dla przykładu utwórzmy prostą klasę który pomoże nam dekorować inne obiekty za pomocą fa (funkcji anonimowej)
class Decorator { private $objectToDecore; public function __construct($object) { $this->objectToDecore = clone $object; } public function decore(callable $decorator) { return $decorator->call($this->objectToDecore); } }
Skoro podstawę dekoratora mamy już utworzoną, czas na prosty obiekt który będziemy dekorować przyjmijmy, że będzie posiadał jedną metodę publiczną która zwróci nam zwykły tekst
class Test { public function getMe() { return 'me'; } }
Na koniec udekorujmy powyższy obiekt w taki sposób aby zwracał tekst "catch me if You can"
zamiast zwykłego "me"
$decorator = new Decorator(new Test()); echo $decorator->decore(function() {return 'catch '.$this->getMe().' if You can';});
I w prosty i w miarę przyjemny sposób udało nam się udekorować metodę getMe()
.
Przedstawiony powyżej (alternatywny) sposób użycia wzorca dekorator, może w wielu przypadkach zastąpić tradycyjny model wzorca z pełną jego kompozycją.
Funkcje zwrotne (Callbacks)
Funkcje zwrotne to naturalne środowisko dla domknięć, można by powiedzieć, że oboje nie potrafią bez siebie żyć. I tak właśnie po trosze jest, ponieważ 90% fa czy też domknięć to tak naprawdę fz (funkcje zwrotne). Możliwości zastosowań fz jest tak dużo, że niestety będę musiał podzielić ten wpis na kilka części, ponieważ dotknięcie tematu fz po macoszemu byłoby wielkim przeoczeniem. Pozwolę sobie rozdzielić podpunkt na temat funkcji zwrotnych na te 'proste’ oraz na takie, które tworzą jakiś większy 'zamysł’. Przykładem rozdzielenia jest powyższy wzorzec projektowy, jeśli dobrze się przypatrzysz nasza funkcja dekorująca była tak naprawdę callbackiem dla metody decore
, ale zaklasyfikowałem ją jako funkcje zwrotną i złożonym 'zamyśle’.
Przykładem zastosowania funkcji zwrotnej może być natywna funkcja języka array_walk
gdzie drugim parametrem jest właśnie callback
, która zostanie wykonana dla każdego elementu wskazanej tablicy, oto i przykład
$array = [1,2,3,4]; array_walk($array, function($item) { echo $item . PHP_EOL;});
Innym przykład użycia funkcji zwrotnej, gdzie wykorzystanie funkcji zwrotnej może być od czegoś uwarunkowane.
function przykladowaFunkcja(callable $callback) { if (rand(0,100) > 69) { return $callback(); } else { return "nie udało się"; } } echo przykladowaFunkcja(function() {return 'juupi';});
Zdarza się także, że funkcje zwrotne przekazuje się do metody lub funkcji, które np. się długo wykonują lub wykonują ważne dla nas rzeczy, poinformowały nas za pomocą funkcji zwrotnej, że już zakończyły swoje obliczenia. Wtedy w metodzie która jest dla nas ważna, przyjmujemy jako parametr funkcje zwrotną i pod sam koniec danej metody świadomie wykonujemy callback
, a oto i przykład
function taFunkcjaWykonujeCiezkieObliczenia(callable $callback) { $liczba = rand(0,100); /** * tutaj jeszcze są jakies obliczenia **/ $callback(); return $liczba; }
Pamiętaj, że nie każda funkcja zwrotna jest funkcją anonimową, ponieważ często funkcja zwrotna przyjmuje typ callable
co umożliwia przekazanie każdej funkcji lub metody. Natomiast każda funkcja anonimowa może być callbackiem.
Koniec części 1
Na koniec ciekawostka. Do PHP (od wersji 7.0) zawitała możliwość wykonania funkcji anonimowej zaraz po jej utworzeniu tzw. self-invoked function. Oto i przykład
(function(){ echo "hello world"; })();
W tej części to tyle, mam nadzieję, że lektura była przyjemna. Z tego miejsca zapraszam na następną część w której będziemy dalej przyglądać się sposobom użycia domknięć, która pojawi się już w przyszłym tygodniu.
Do zobaczenia !
Fajny artykuł, ale będę polemizował co do stwierdzenie, że podany przykład spełnia wszelkie znamiona Decorator Pattern. Bez implementacji interfaceu klasy dekorowanej, dekorator nie może zostać użyty przeźroczyście w jego miejscu.
Bardzo natomiast podoba mi się przykład z Template Engine i wykorzystanie metody Closure::call(). Dobra robota 🙂
Całkowicie masz rację, dlatego starałem się zaznaczyć, że jest to alternatywny sposób i jego prosta implementacja bardziej skłaniająca się ku dekorowaniu już na samym końcu łańcucha wywołań. Dodatkowo wszystko też zależy od tego z jakim kodem mamy do czynienia i jak chcemy dekorować. Bo dla przykładu gdzie metody danego obiektu modyfikują jego stan i zwracają siebie pozwalając na łańcuchowe wywołania, taki przykład jak najbardziej by miał racje bytu.
Oczywiście zgadzam się, że nie jest to architektonicznie typowy Dekorator ale jakaś alternatywa owszem 😉
Jeśli chodzi o Fluent interface a decoratory, to polecam artykuł https://ocramius.github.io/blog/fluent-interfaces-are-evil/ 😉