Merge branch 'master' of github.com:domnikl/DesignPatternsPHP

This commit is contained in:
Daniel González Cerviño 2015-09-10 10:15:13 +02:00
commit ba14231163
9 changed files with 266 additions and 69 deletions

View File

@ -9,7 +9,7 @@ class BookList implements \Countable
public function getBook($bookNumberToGet)
{
if ((int)$bookNumberToGet <= $this->count()) {
if (isset($this->books[$bookNumberToGet])) {
return $this->books[$bookNumberToGet];
}

View File

@ -8,7 +8,7 @@ class BookListIterator implements \Iterator
/**
* @var BookList
*/
protected $bookList;
private $bookList;
/**
* @var int
@ -61,7 +61,7 @@ class BookListIterator implements \Iterator
*/
public function valid()
{
return $this->currentBook < $this->bookList->count();
return null !== $this->bookList->getBook($this->currentBook);
}
/**

View File

@ -2,22 +2,77 @@
namespace DesignPatterns\Behavioral\Iterator;
class BookListReverseIterator extends BookListIterator
class BookListReverseIterator implements \Iterator
{
/**
* @var BookList
*/
private $bookList;
/**
* @var int
*/
protected $currentBook = 0;
public function __construct(BookList $bookList)
{
$this->bookList = $bookList;
$this->currentBook = $this->bookList->count() - 1;
}
/**
* Return the current book
* @link http://php.net/manual/en/iterator.current.php
* @return Book Can return any type.
*/
public function current()
{
return $this->bookList->getBook($this->currentBook);
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Move forward to next element
* @link http://php.net/manual/en/iterator.next.php
* @return void Any returned value is ignored.
*/
public function next()
{
$this->currentBook--;
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Return the key of the current element
* @link http://php.net/manual/en/iterator.key.php
* @return mixed scalar on success, or null on failure.
*/
public function key()
{
return $this->currentBook;
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Checks if current position is valid
* @link http://php.net/manual/en/iterator.valid.php
* @return boolean The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure.
*/
public function valid()
{
return 0 <= $this->currentBook;
return null !== $this->bookList->getBook($this->currentBook);
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Rewind the Iterator to the first element
* @link http://php.net/manual/en/iterator.rewind.php
* @return void Any returned value is ignored.
*/
public function rewind()
{
$this->currentBook = $this->bookList->count() - 1;
}
}

View File

@ -4,12 +4,26 @@ namespace DesignPatterns\Behavioral\Memento;
class Caretaker
{
public static function run()
protected $history = array();
/**
* @return Memento
*/
public function getFromHistory($id)
{
/* @var $savedStates Memento[] */
return $this->history[$id];
}
$savedStates = array();
/**
* @param Memento $state
*/
public function saveToHistory(Memento $state)
{
$this->history[] = $state;
}
public function runCustomLogic()
{
$originator = new Originator();
//Setting state to State1
@ -17,17 +31,20 @@ class Caretaker
//Setting state to State2
$originator->setState("State2");
//Saving State2 to Memento
$savedStates[] = $originator->saveToMemento();
$this->saveToHistory($originator->getStateAsMemento());
//Setting state to State3
$originator->setState("State3");
// We can request multiple mementos, and choose which one to roll back to.
// Saving State3 to Memento
$savedStates[] = $originator->saveToMemento();
$this->saveToHistory($originator->getStateAsMemento());
//Setting state to State4
$originator->setState("State4");
$originator->restoreFromMemento($savedStates[1]);
$originator->restoreFromMemento($this->getFromHistory(1));
//State after restoring from Memento: State3
return $originator->getStateAsMemento()->getState();
}
}

View File

@ -15,14 +15,17 @@ class Originator
*/
public function setState($state)
{
// you must check type of state inside child of this class
// or use type-hinting for full pattern implementation
$this->state = $state;
}
/**
* @return Memento
*/
public function saveToMemento()
public function getStateAsMemento()
{
// you must save a separate copy in Memento
$state = is_object($this->state) ? clone $this->state : $this->state;
return new Memento($state);

View File

@ -4,26 +4,43 @@
Purpose
-------
Provide the ability to restore an object to its previous state (undo via
rollback).
It provides the ability to restore an object to it's previous state (undo
via rollback) or to gain access to state of the object, without revealing
it's implementation (i.e., the object is not required to have a functional
for return the current state).
The memento pattern is implemented with three objects: the originator, a
caretaker and a memento. The originator is some object that has an
internal state. The caretaker is going to do something to the
originator, but wants to be able to undo the change. The caretaker first
asks the originator for a memento object. Then it does whatever
operation (or sequence of operations) it was going to do. To roll back
to the state before the operations, it returns the memento object to the
originator. The memento object itself is an opaque object (one which the
caretaker cannot, or should not, change). When using this pattern, care
should be taken if the originator may change other objects or resources
- the memento pattern operates on a single object.
The memento pattern is implemented with three objects: the Originator, a
Caretaker and a Memento.
Memento an object that *contains a concrete unique snapshot of state* of
any object or resource: string, number, array, an instance of class and so on.
The uniqueness in this case does not imply the prohibition existence of similar
states in different snapshots. That means the state can be extracted as
the independent clone. Any object stored in the Memento should be
*a full copy of the original object rather than a reference* to the original
object. The Memento object is a "opaque object" (the object that no one can
or should change).
Originator it is an object that contains the *actual state of an external
object is strictly specified type*. Originator is able to create a unique
copy of this state and return it wrapped in a Memento. The Originator does
not know the history of changes. You can set a concrete state to Originator
from the outside, which will be considered as actual. The Originator must
make sure that given state corresponds the allowed type of object. Originator
may (but not should) have any methods, but they *they can't make changes to
the saved object state*.
Caretaker *controls the states history*. He may make changes to an object;
take a decision to save the state of an external object in the Originator;
ask from the Originator snapshot of the current state; or set the Originator
state to equivalence with some snapshot from history.
Examples
--------
- The seed of a pseudorandom number generator
- The state in a finite state machine
- Control for intermediate states of `ORM Model <http://en.wikipedia.org/wiki/Object-relational_mapping>`_ before saving
UML Diagram
-----------

View File

@ -2,6 +2,8 @@
namespace DesignPatterns\Behavioral\Memento\Tests;
use DesignPatterns\Behavioral\Memento\Caretaker;
use DesignPatterns\Behavioral\Memento\Memento;
use DesignPatterns\Behavioral\Memento\Originator;
/**
@ -10,6 +12,37 @@ use DesignPatterns\Behavioral\Memento\Originator;
class MementoTest extends \PHPUnit_Framework_TestCase
{
public function testUsageExample()
{
$originator = new Originator();
$caretaker = new Caretaker();
$character = new \stdClass();
$character->name = "Gandalf"; // new object
$originator->setState($character); // connect Originator to character object
$character->name = "Gandalf the Grey"; // work on the object
$character->race = "Maia"; // still change something
$snapshot = $originator->getStateAsMemento(); // time to save state
$caretaker->saveToHistory($snapshot); // put state to log
$character->name = "Sauron"; // change something
$character->race = "Ainur"; // and again
$this->assertAttributeEquals($character, "state", $originator); // state inside the Originator was equally changed
$snapshot = $originator->getStateAsMemento(); // time to save another state
$caretaker->saveToHistory($snapshot); // put state to log
$rollback = $caretaker->getFromHistory(0);
$originator->restoreFromMemento($rollback); // return to first state
$character = $rollback->getState(); // use character from old state
$this->assertEquals("Gandalf the Grey", $character->name); // yes, that what we need
$character->name = "Gandalf the White"; // make new changes
$this->assertAttributeEquals($character, "state", $originator); // and Originator linked to actual object again
}
public function testStringState()
{
$originator = new Originator();
@ -18,52 +51,94 @@ class MementoTest extends \PHPUnit_Framework_TestCase
$this->assertAttributeEquals("State1", "state", $originator);
$originator->setState("State2");
$this->assertAttributeEquals("State2", "state", $originator);
$savedState = $originator->saveToMemento();
$this->assertAttributeEquals("State2", "state", $savedState);
$snapshot = $originator->getStateAsMemento();
$this->assertAttributeEquals("State2", "state", $snapshot);
$originator->setState("State3");
$this->assertAttributeEquals("State3", "state", $originator);
$originator->restoreFromMemento($savedState);
$originator->restoreFromMemento($snapshot);
$this->assertAttributeEquals("State2", "state", $originator);
}
public function testObjectState()
public function testSnapshotIsClone()
{
$originator = new Originator();
$object = new \stdClass();
$originator->setState($object);
$snapshot = $originator->getStateAsMemento();
$object->new_property = 1;
$this->assertAttributeEquals($object, "state", $originator);
$this->assertAttributeNotEquals($object, "state", $snapshot);
$originator->restoreFromMemento($snapshot);
$this->assertAttributeNotEquals($object, "state", $originator);
}
public function testCanChangeActualState()
{
$originator = new Originator();
$first_state = new \stdClass();
$originator->setState($first_state);
$snapshot = $originator->getStateAsMemento();
$second_state = $snapshot->getState();
$first_state->first_property = 1; // still actual
$second_state->second_property = 2; // just history
$this->assertAttributeEquals($first_state, "state", $originator);
$this->assertAttributeNotEquals($second_state, "state", $originator);
$originator->restoreFromMemento($snapshot);
$first_state->first_property = 11; // now it lost state
$second_state->second_property = 22; // must be actual
$this->assertAttributeEquals($second_state, "state", $originator);
$this->assertAttributeNotEquals($first_state, "state", $originator);
}
public function testStateWithDifferentObjects()
{
$originator = new Originator();
$foo = new \stdClass();
$foo->data = "foo";
$first = new \stdClass();
$first->data = "foo";
$originator->setState($foo);
$originator->setState($first);
$this->assertAttributeEquals($first, "state", $originator);
$this->assertAttributeEquals($foo, "state", $originator);
$first_snapshot = $originator->getStateAsMemento();
$this->assertAttributeEquals($first, "state", $first_snapshot);
$savedState = $originator->saveToMemento();
$second = new \stdClass();
$second->data = "bar";
$originator->setState($second);
$this->assertAttributeEquals($second, "state", $originator);
$this->assertAttributeEquals($foo, "state", $savedState);
$originator->restoreFromMemento($first_snapshot);
$this->assertAttributeEquals($first, "state", $originator);
}
$bar = new \stdClass();
$bar->data = "bar";
public function testCaretaker()
{
$caretaker = new Caretaker();
$memento1 = new Memento("foo");
$memento2 = new Memento("bar");
$caretaker->saveToHistory($memento1);
$caretaker->saveToHistory($memento2);
$this->assertAttributeEquals(array($memento1, $memento2), "history", $caretaker);
$this->assertEquals($memento1, $caretaker->getFromHistory(0));
$this->assertEquals($memento2, $caretaker->getFromHistory(1));
$originator->setState($bar);
}
$this->assertAttributeEquals($bar, "state", $originator);
$originator->restoreFromMemento($savedState);
$this->assertAttributeEquals($foo, "state", $originator);
$foo->data = null;
$this->assertAttributeNotEquals($foo, "state", $savedState);
$this->assertAttributeNotEquals($foo, "state", $originator);
public function testCaretakerCustomLogic()
{
$caretaker = new Caretaker();
$result = $caretaker->runCustomLogic();
$this->assertEquals("State3", $result);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View File

@ -1,29 +1,32 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: DesignPatternsPHP 1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-29 12:18+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"PO-Revision-Date: 2015-05-30 01:42+0300\n"
"Last-Translator: Eugene Glotov <kivagant@gmail.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru\n"
#: ../../Behavioral/Memento/README.rst:2
msgid "`Memento`__"
msgstr ""
msgstr "`Хранитель`__"
#: ../../Behavioral/Memento/README.rst:5
msgid "Purpose"
msgstr ""
msgstr "Назначение"
#: ../../Behavioral/Memento/README.rst:7
msgid ""
"Provide the ability to restore an object to its previous state (undo via "
"rollback)."
msgstr ""
"Предоставляет возможность восстановить объект в его предыдущем состоянии или "
"получить доступ к состоянию объекта, не раскрывая его реализацию (т.е. сам "
"объект не обязан иметь функционал возврата текущего состояния)."
#: ../../Behavioral/Memento/README.rst:10
msgid ""
@ -39,47 +42,74 @@ msgid ""
"other objects or resources - the memento pattern operates on a single "
"object."
msgstr ""
"Паттерн «Хранитель» реализуется тремя объектами: Создатель, Опекун и "
"Хранитель.\n"
"\n"
"Хранитель — объект, который *хранит конкретный уникальный слепок состояния* "
"любого объекта или ресурса: строка, число, массив, экземпляр класса и так "
"далее. Уникальность в данном случае подразумевает не запрет существования "
"одинаковых состояний в слепках, а то, что состояние можно извлечь в виде "
"независимого клона. Это значит, объект, сохраняемый в Хранитель, должен *быть "
"полной копией исходного объекта а не ссылкой* на исходный объект. Сам объект "
"Хранитель является «непрозрачным объектом» (тот, который никто не может и не "
"должен изменять).\n"
"\n"
"Создатель — это объект, который *содержит в себе актуальное состояние внешнего "
"объекта строго заданного типа* и умеет создать уникальную копию этого "
"состояния, возвращая её обёрнутую в Хранитель. Создатель не знает истории "
"изменений. Создателю можно принудительно установить конкретное состояние "
"извне, которое будет считаться актуальным. Создатель должен позаботиться, "
"чтобы это состояние соответствовало типу объекта, с которым ему разрешено "
"работать. Создатель может (но не обязан) иметь любые методы, но они *не могут "
"менять сохранённое состояние объекта*.\n"
"\n"
"Опекун *управляет историей слепков состояний*. Он может вносить изменения в "
"объект, принимать решение о сохранении состояния внешнего объекта в Создателе, "
"требовать от Создателя слепок текущего состояния, или привести состояние "
"Создателя в соответствие состоянию какого-то слепка из истории."
#: ../../Behavioral/Memento/README.rst:23
msgid "Examples"
msgstr ""
msgstr "Примеры"
#: ../../Behavioral/Memento/README.rst:25
msgid "The seed of a pseudorandom number generator"
msgstr ""
"`Семя <http://en.wikipedia.org/wiki/Random_seed>`_ псевдослучайного генератора "
"чисел."
#: ../../Behavioral/Memento/README.rst:26
msgid "The state in a finite state machine"
msgstr ""
msgstr "Состояние конечного автомата"
#: ../../Behavioral/Memento/README.rst:29
msgid "UML Diagram"
msgstr ""
msgstr "UML Диаграмма"
#: ../../Behavioral/Memento/README.rst:36
msgid "Code"
msgstr ""
msgstr "Код"
#: ../../Behavioral/Memento/README.rst:38
msgid "You can also find these code on `GitHub`_"
msgstr ""
msgstr "Вы можете найти этот код на `GitHub`_"
#: ../../Behavioral/Memento/README.rst:40
msgid "Memento.php"
msgstr ""
msgstr "Memento.php"
#: ../../Behavioral/Memento/README.rst:46
msgid "Originator.php"
msgstr ""
msgstr "Originator.php"
#: ../../Behavioral/Memento/README.rst:52
msgid "Caretaker.php"
msgstr ""
msgstr "Caretaker.php"
#: ../../Behavioral/Memento/README.rst:59
msgid "Test"
msgstr ""
msgstr "Тест"
#: ../../Behavioral/Memento/README.rst:61
msgid "Tests/MementoTest.php"
msgstr ""
msgstr "Tests/MementoTest.php"