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...