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.
Czytaj dalej tutaj (rozwija treść wpisu)
Czytaj dalej na blogu autora...

Zwiń
Czytaj na blogu autora...