备忘录模式
目的
备忘录模式,可以在不暴露对象具体实现细节的情况下,提供了将一个对象恢复到之前的状态(通过回滚来取消)或获取对象状态的能力(即:不要求对象有返回当前状态的方法)。
该模式由三个对象构成:Originator、Caretaker 和 Memento。
- Memento:是一个对象,它包含任何对象(或资源)的具体的唯一状态快照:字符串、数字、数组、类实例等。在这种情况下,唯一性并不意味着禁止在不同快照中存在类似的状态,而是指状态可以作为独立的克隆体提取出来。存储在 Memento 中的任何对象都应该是原始对象的完整副本,而不是对原始对象的引用。Memento 对象是一个“不透明的对象”,任何人都不能也不应该改变这个对象。
- Originator:它是一个包含了严格类型的外部对象实际状态的对象。Originator 能够创建该状态的唯一副本,并将其包裹在 Memento 中返回。Originator 并不了解状态变化的历史。你可以从外部给 Originator 设置一个具体的状态,这将被视为实际状态。Originator 必须确保给定的状态与允许的对象类型相对应。Originator 可以(但不应该)拥有任何方法,但他们不能对已保存的对象的状态进行更改。
- Caretaker:该对象用于控制状态历史。它可以对一个对象的状态进行修改;也可以决定将外部对象的状态保存在 Originator 中;还可以获取 Originator 当前状态的快照;或者,将 Originator 的状态设置为与历史中的某个快照一样的状态。
使用场景
- 伪随机数生成器的种子
- 有限状态机中的状态
- ORM 模型保存数据前中间状态的控制
UML 类图

代码
Memento.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php declare(strict_types = 1);
namespace DesignPatterns\Behavioral\Memento;
class Memento { private State $state;
public function __construct(State $stateToSave) { $this->state = $stateToSave; }
public function getState(): State { return $this->state; } }
|
State.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| <?php declare(strict_types = 1);
namespace DesignPatterns\Behavioral\Memento;
use InvalidArgumentException;
class State { const STATE_CREATED = 'created'; const STATE_OPENED = 'opened'; const STATE_ASSIGNED = 'assigned'; const STATE_CLOSED = 'closed';
private string $state;
private static array $validStates = [ self::STATE_CREATED, self::STATE_OPENED, self::STATE_ASSIGNED, self::STATE_CLOSED, ];
public function __construct(string $state) { self::ensureIsValidState($state);
$this->state = $state; }
private static function ensureIsValidState(string $state) { if (!in_array($state, self::$validStates)) { throw new InvalidArgumentException('Invalid state given'); } }
public function __toString(): string { return $this->state; } }
|
Ticket.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <?php declare(strict_types = 1);
namespace DesignPatterns\Behavioral\Memento;
class Ticket { private State $currentState;
public function __construct() { $this->currentState = new State(State::STATE_CREATED); }
public function open() { $this->currentState = new State(State::STATE_OPENED); }
public function assign() { $this->currentState = new State(State::STATE_ASSIGNED); }
public function close() { $this->currentState = new State(State::STATE_CLOSED); }
public function saveToMemento(): Memento { return new Memento(clone $this->currentState); }
public function restoreFromMemento(Memento $memento) { $this->currentState = $memento->getState(); }
public function getState(): State { return $this->currentState; } }
|
测试
Tests/MementoTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <?php declare(strict_types = 1);
namespace DesignPatterns\Behavioral\Memento\Tests;
use DesignPatterns\Behavioral\Memento\State; use DesignPatterns\Behavioral\Memento\Ticket; use PHPUnit\Framework\TestCase;
class MementoTest extends TestCase { public function testOpenTicketAssignAndSetBackToOpen() { $ticket = new Ticket();
$ticket->open(); $openedState = $ticket->getState(); $this->assertSame(State::STATE_OPENED, (string) $ticket->getState());
$memento = $ticket->saveToMemento();
$ticket->assign(); $this->assertSame(State::STATE_ASSIGNED, (string) $ticket->getState());
$ticket->restoreFromMemento($memento);
$this->assertSame(State::STATE_OPENED, (string) $ticket->getState()); $this->assertNotSame($openedState, $ticket->getState()); } }
|