3.6. Memento (Lembrança)
3.6.1. Objetivo
Ele provê a habilidade de restaurar um objeto para seu estado anterior (desfazer via rollback) ou ganhar acesso ao estado do objeto sem revelar sua implementação (p.e. o objeto não é obrigado a ter uma funcionalidade para retornar ao estado atual).
O padrão Memento é implementado com três objetos: o Originator, um Caretaker e um Memento.
Memento - um objeto que contém um snapshot único e concreto do estado de qualquer objeto ou recurso: string, número, array, uma instance de classe e assim por diante. A singularidade, neste caso, não implica a proibição da existência de estados semelhantes em diferentes snapshots. Isso significa que o estado pode ser extraído como o clone independente. Qualquer objeto armazenado no Memento deve ser uma cópia completa do objeto original em vez de uma referência para o objeto original. O objeto Memento é um “objeto opaco” (o objeto que ninguém pode ou deve mudar).
Originator - é um objeto que contém o estado atual de um objeto externo é estritamente o tipo especificado. Originator é capaz de criar uma cópia única deste estado e devolvê-lo envolto em um Memento. O O Originator não conhece a história das mudanças. Você pode definir um estado concreto ao Originator do lado de fora, que será considerado como atual. O Originator deve certificar-se de que determinado estado corresponde ao tipo permitido de objeto. Originator pode (mas não deve) ter quaisquer métodos, mas eles não podem fazer alterações no estado do objeto salvo.
Caretaker controla a história dos estados. Ele pode fazer alterações em um objeto; tomar uma decisão para salvar o estado de um objeto externo no Originator; pedir a partir do snapshot do Originator do estado atual ou definir o estado do Originator para equivalência com algum snapshot do histórico.
3.6.2. Exemplos
A semente de um gerador de números pseudo-aleatórios
O estado em uma máquina de estados finitos
Controle para estados intermediários de ORM Model antes de salvar
3.6.3. Diagrama UML
3.6.4. Código
Você também pode encontrar este código no GitHub
Memento.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Memento;
6
7class Memento
8{
9 public function __construct(private State $state)
10 {
11 }
12
13 public function getState(): State
14 {
15 return $this->state;
16 }
17}
State.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Memento;
6
7use InvalidArgumentException;
8
9class State implements \Stringable
10{
11 public const STATE_CREATED = 'created';
12 public const STATE_OPENED = 'opened';
13 public const STATE_ASSIGNED = 'assigned';
14 public const STATE_CLOSED = 'closed';
15
16 private string $state;
17
18 /**
19 * @var string[]
20 */
21 private static array $validStates = [
22 self::STATE_CREATED,
23 self::STATE_OPENED,
24 self::STATE_ASSIGNED,
25 self::STATE_CLOSED,
26 ];
27
28 public function __construct(string $state)
29 {
30 self::ensureIsValidState($state);
31
32 $this->state = $state;
33 }
34
35 private static function ensureIsValidState(string $state)
36 {
37 if (!in_array($state, self::$validStates)) {
38 throw new InvalidArgumentException('Invalid state given');
39 }
40 }
41
42 public function __toString(): string
43 {
44 return $this->state;
45 }
46}
Ticket.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Memento;
6
7/**
8 * Ticket is the "Originator" in this implementation
9 */
10class Ticket
11{
12 private State $currentState;
13
14 public function __construct()
15 {
16 $this->currentState = new State(State::STATE_CREATED);
17 }
18
19 public function open()
20 {
21 $this->currentState = new State(State::STATE_OPENED);
22 }
23
24 public function assign()
25 {
26 $this->currentState = new State(State::STATE_ASSIGNED);
27 }
28
29 public function close()
30 {
31 $this->currentState = new State(State::STATE_CLOSED);
32 }
33
34 public function saveToMemento(): Memento
35 {
36 return new Memento(clone $this->currentState);
37 }
38
39 public function restoreFromMemento(Memento $memento)
40 {
41 $this->currentState = $memento->getState();
42 }
43
44 public function getState(): State
45 {
46 return $this->currentState;
47 }
48}
3.6.5. Teste
Tests/MementoTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Memento\Tests;
6
7use DesignPatterns\Behavioral\Memento\State;
8use DesignPatterns\Behavioral\Memento\Ticket;
9use PHPUnit\Framework\TestCase;
10
11class MementoTest extends TestCase
12{
13 public function testOpenTicketAssignAndSetBackToOpen()
14 {
15 $ticket = new Ticket();
16
17 // open the ticket
18 $ticket->open();
19 $openedState = $ticket->getState();
20 $this->assertSame(State::STATE_OPENED, (string) $ticket->getState());
21
22 $memento = $ticket->saveToMemento();
23
24 // assign the ticket
25 $ticket->assign();
26 $this->assertSame(State::STATE_ASSIGNED, (string) $ticket->getState());
27
28 // now restore to the opened state, but verify that the state object has been cloned for the memento
29 $ticket->restoreFromMemento($memento);
30
31 $this->assertSame(State::STATE_OPENED, (string) $ticket->getState());
32 $this->assertNotSame($openedState, $ticket->getState());
33 }
34}