Autor wpisu: Śpiechu, dodany: 02.01.2011 21:47, tagi: php
To pierwszy wpis w tym roku, mam nadzieję, że wyda się Wam na poziomie. Czytam co nieco na temat wzorca memento i trochę ubolewam, że nie znam C++, gdyż wszystkie przykłady we Wzorcach projektowych1 napisane są właśnie w tym języku (część gwiazdek i innych dwukropków w kodzie jest dla mnie trochę niezrozumiała).
Na warsztat wziąłem dzisiaj wzorzec memento (wg mnie tłumaczenie jako „pamiątka” jest trochę śmieszne; memento to memento i koniec). Za pomocą tego wzorca możemy sporządzić coś na kształt kopii przywracania stanu obiektu. Ma to zastosowanie głównie jako mechanizm wstecz/cofnij w standardowych aplikacjach. Jak ktoś się uprze, to na pewno w PHP również znajdzie zastosowanie.
Napisanie kodu obiektu trzymającego stan innego obiektu jest dosyć proste, dlatego podniosłem trochę poprzeczkę o sprawdzanie kto wywołuje dany obiekt i czy dane memento jest na pewno dla niego przeznaczone. W/w mądra książka mówi do mnie, że tworzyć i przywracać stan ma obiekt, którego stan dotyczy. Za to przechowywaniem stanów zajmować się odrębny obiekt — CareTaker
(u mnie PrzechowywaczMemento
).
Najpierw idzie obiekt zainteresowany przechowywaniem swojego stanu. Stan każdego obiektu to przypisane wartości do jego zmiennych. U mnie będzie to tylko zmienna $komunikat
.
class JakisObiekt { /** * @var string komunikat do wyswietlenia */ protected $komunikat; /** * @return string komunikat */ public function getKomunikat() { return $this->komunikat; } /** * @param string $k komunikat do ustawienia */ public function setKomunikat($k) { $this->komunikat = $k; } /** * @return Memento memento z aktualnym komunikatem obiektu */ public function getMemento() { return new Memento($this); } /** * @param Memento $memento zawierajace poprzedni komunikat */ public function setMemento(Memento $memento) { try { $this->komunikat = $memento->getKomunikat(spl_object_hash($this)) . ' (przywrocony z Memento)'; } catch (Exception $e) { echo 'Nie udalo sie przywrocic poprzedniego stanu: ' . $e->getMessage(); } } }
Zainteresować może was funkcja spl_object_hash
. Zwraca unikalny identyfikator (hash) danego obiektu. Memento zapisuje sobie identyfikator przy tworzeniu, a przy chęci wywołania getKomunikat()
wymaga podanie hasha w celu sprawdzenia czy dane memento jest na pewno dla niego.
Dalej idzie kod tytułowego memento, czyli obiekt przechowujący stan obiektu. Jest ściśle powiązane z obiektem JakisObiekt
(lub jego potomkami).
class Memento { /** * @var string hash obiektu tworzacego memento */ private $hash; /** * @var string przechowywany komunikat */ private $komunikat; public function __construct(JakisObiekt $ob) { if ($this->czyLegalnyWywolujacy() === true) { $this->hash = spl_object_hash($ob); $this->komunikat = $ob->getKomunikat(); } else { throw new Exception('Tylko obiekt z rodziny JakisObiekt moze tworzyc klase Memento'); } } public function getKomunikat($hash) { if ($this->czyLegalnyWywolujacy() !== true) { throw new Exception('Tylko obiekt z rodziny JakisObiekt moze wywolac funkcje getKomunikat()'); } if ($this->hash !== $hash) { throw new Exception('Hash obiektu tworzacego Memento i wywolujacego getKomunikat() nie zgadza sie'); } return $this->komunikat; } private function czyLegalnyWywolujacy() { $trace = debug_backtrace(true); if (!empty($trace[2]['object']) && is_a($trace[2]['object'],'JakisObiekt')) { return true; } else { return false; } } }
Tu z kolei powinna Was zainteresować funkcja czyLegalnyWywolujacy()
. To cudo wywołuje (prawdopodobnie) zasobożerną funkcję debug_backtrace
i sprawdza kto 2 kroki wcześniej poprosił o wywołanie funkcji. Jeżeli jest to kto inny niż obiekt typu JakisObiekt
to się obraża i daje sygnał do wywalenia wyjątku. Fajne, co?
Dla sprawdzenia czy memento jest w stanie przyjmować żądania od obiektów dziedziczonych po JakisObiekt
tworzymy sobie obiekt Dziedziczony
.
class Dziedziczony extends JakisObiekt { }
Na koniec podaję kod przechowywacza, który zajmuje się zapisem kolejnych wersji i odtwarzaniem ich. Po niewielkich przeróbkach kod może stać się w zasadzie uniwersalny.
class PrzechowywaczMemento { /** * @var array tablica asocjacyjna o konstrukcji hash => memento */ private $stany; public function __construct() { $this->stany = array(); } public function zapiszStan(JakisObiekt $jo) { $this->stany[spl_object_hash($jo)][] = $jo->getMemento(); } public function przywrocStan(JakisObiekt $jo) { $hash = spl_object_hash($jo); $znalezionyStan = (!empty($this->stany[$hash])) ? array_pop($this->stany[$hash]) : null; if ($znalezionyStan !== null) { $jo->setMemento($znalezionyStan); } else { echo 'Brak zapisanego wczesniejszego stanu dla tego obiektu <br />'; } } public function oczyscRejestrMemento() { foreach ($this->stany as $key => $val) { if (count($val) == 0) { unset($this->stany[$key]); } } } }
Metoda przywrocStan()
może być trochę zawiła. Najpierw sprawdza czy klucz z haszem podanego w parametrze obiektu w ogóle istnieje. Jeżeli tak to przywraca stan, a jeżeli nie to wypisuje komunikat. Na koniec mamy bezparametrową metodę oczyscRejestrMemento()
. Jeżeli przez przechowywacza przeszło wiele obiektów, robi się śmietnik kluczy niezawierających stanów do przywrócenia.