备忘录模式(Memento)

目的

它提供了在不破坏封装(对象不需要具有返回当前状态的函数)的情况下恢复到之前状态(使用回滚)或者获取对象的内部状态。

备忘录模式使用 3 个类来实现:Originator,Caretaker 和 Memento。

Memento —— 负责存储 Originator 的 唯一内部状态 ,它可以包含: string,number, array,类的实例等等。Memento 「不是公开的类」(任何人都不应该且不能更改它),并防止 Originator 以外的对象访问它,它提供2个接口:Caretaker 只能看到备忘录的窄接口,他只能将备忘录传递给其他对象。Originator 却可看到备忘录的宽接口,允许它访问返回到先前状态所需要的所有数据。

Originator —— 它负责创建 Memento ,并记录 外部对象当前时刻的状态, 并可使用 Memento 恢复内部状态。Originator 可根据需要决定 Memento 存储 Originator 的哪些内部状态。 Originator 也许(不是应该)有自己的方法(methods)。 但是,他们 不能更改已保存对象的当前状态

Caretaker —— 负责保存 Memento。 它可以修改一个对象;决定 Originator 中对象当前时刻的状态; 从 Originator 获取对象的当前状态; 或者回滚 Originator 中对象的状态。

例子

  • 发送一个随机数
  • 并将这个随机数存在时序机中
  • 保存之前控制 ORM Model 中的状态

UML 图

file

代码

你可以在 GitHub 查看这段代码。

Memento.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Memento;
  3. class Memento
  4. {
  5. /**
  6. * @var State
  7. */
  8. private $state;
  9. /**
  10. * @param State $stateToSave
  11. */
  12. public function __construct(State $stateToSave)
  13. {
  14. $this->state = $stateToSave;
  15. }
  16. /**
  17. * @return State
  18. */
  19. public function getState()
  20. {
  21. return $this->state;
  22. }
  23. }

State.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Memento;
  3. class State
  4. {
  5. const STATE_CREATED = 'created';
  6. const STATE_OPENED = 'opened';
  7. const STATE_ASSIGNED = 'assigned';
  8. const STATE_CLOSED = 'closed';
  9. /**
  10. * @var string
  11. */
  12. private $state;
  13. /**
  14. * @var string[]
  15. */
  16. private static $validStates = [
  17. self::STATE_CREATED,
  18. self::STATE_OPENED,
  19. self::STATE_ASSIGNED,
  20. self::STATE_CLOSED,
  21. ];
  22. /**
  23. * @param string $state
  24. */
  25. public function __construct(string $state)
  26. {
  27. self::ensureIsValidState($state);
  28. $this->state = $state;
  29. }
  30. private static function ensureIsValidState(string $state)
  31. {
  32. if (!in_array($state, self::$validStates)) {
  33. throw new \InvalidArgumentException('Invalid state given');
  34. }
  35. }
  36. public function __toString(): string
  37. {
  38. return $this->state;
  39. }
  40. }

Ticket.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Memento;
  3. /**
  4. * Ticket 是 Originator 的原始副本
  5. */
  6. class Ticket
  7. {
  8. /**
  9. * @var State
  10. */
  11. private $currentState;
  12. public function __construct()
  13. {
  14. $this->currentState = new State(State::STATE_CREATED);
  15. }
  16. public function open()
  17. {
  18. $this->currentState = new State(State::STATE_OPENED);
  19. }
  20. public function assign()
  21. {
  22. $this->currentState = new State(State::STATE_ASSIGNED);
  23. }
  24. public function close()
  25. {
  26. $this->currentState = new State(State::STATE_CLOSED);
  27. }
  28. public function saveToMemento(): Memento
  29. {
  30. return new Memento(clone $this->currentState);
  31. }
  32. public function restoreFromMemento(Memento $memento)
  33. {
  34. $this->currentState = $memento->getState();
  35. }
  36. public function getState(): State
  37. {
  38. return $this->currentState;
  39. }
  40. }

测试

Tests/MementoTest.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Memento\Tests;
  3. use DesignPatterns\Behavioral\Memento\State;
  4. use DesignPatterns\Behavioral\Memento\Ticket;
  5. use PHPUnit\Framework\TestCase;
  6. class MementoTest extends TestCase
  7. {
  8. public function testOpenTicketAssignAndSetBackToOpen()
  9. {
  10. $ticket = new Ticket();
  11. // 打开 ticket
  12. $ticket->open();
  13. $openedState = $ticket->getState();
  14. $this->assertEquals(State::STATE_OPENED, (string) $ticket->getState());
  15. $memento = $ticket->saveToMemento();
  16. // 分配 ticket
  17. $ticket->assign();
  18. $this->assertEquals(State::STATE_ASSIGNED, (string) $ticket->getState());
  19. // 现在已经恢复到已打开的状态,但需要验证是否已经克隆了当前状态作为副本
  20. $ticket->restoreFromMemento($memento);
  21. $this->assertEquals(State::STATE_OPENED, (string) $ticket->getState());
  22. $this->assertNotSame($openedState, $ticket->getState());
  23. }
  24. }