O Padrão de Projeto Memento é utilizado quando queremos externalizar o estado de um objeto sem violar o encapsulamento, de modo que podemos restaurar este estado posteriormente se necessário. Vale ressaltar que cada objeto possui seus próprios atributos em suas respectivas classes, o que define o estado de um objeto são os valores que são atribuídos a estes atributos.

Podemos refinar um pouco mais a definição deste pattern… Dizendo que um Memento é um objeto que armazena um estado interno de outro objeto podendo restaurar seu estado anterior. São três participantes, cada um com uma especificidade:
- Originator: Objeto original que terá seu estado guardado
- Memento: Resolve a forma e quais dados devem ser guardados
- Caretaker: Guarda o estado
Um exemplo ótimo que utiliza este pattern está no Control + Z utilizado nos editores de texto. Você armazena o estado anterior e consegue criar um snapshot dos estados possibilitando a reversão dos dados anteriores.
Para este exemplo, a classe Editor vai representar o Originator, o estado vai ser controlado pelo History, que representa o Caretaker, e a forma como os dados serão gravados fica a encargo do EditorState.

<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Behavioral\Memento;
interface EditorMemento
{
public function getContent(): string;
}
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Behavioral\Memento;
final class EditorState implements EditorMemento
{
private string $content;
public function __construct(string $content)
{
$this->content = $content;
}
public function getContent(): string
{
return $this->content;
}
}
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Behavioral\Memento;
class Editor
{
private string $content;
public function getContent(): string
{
return $this->content;
}
public function setContent(string $content): void
{
$this->content = $content;
}
public function save(): EditorMemento
{
return new EditorState($this->content);
}
public function restore(EditorMemento $memento): void
{
$this->content = $memento->getContent();
}
}
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Behavioral\Memento;
use RuntimeException;
use SplDoublyLinkedList;
//Caretaker
final class History
{
public function __construct(
private SplDoublyLinkedList $states = new SplDoublyLinkedList()
) {
}
public function save(EditorMemento $memento): void
{
$this->states->push($memento);
}
public function restore(Editor $editor): void
{
if ($this->states->isEmpty()) {
throw new RuntimeException('No states to restore');
}
$this->states->pop();
$editor->restore($this->states->top());
}
}
Uma das grandes vantagens do Padrão Memento é que sem violar o encapsulamento de capturar o estado interno de um objeto, conseguimos salvar o estado fora do objeto. Isso é muito útil quando queremos salvar o estado fora do objeto para mais tarde conseguir restaurar o estado anterior. Ou seja, este comportamento está intimamente ligado quando queremos dar permissão para um utilizador para cancelar uma operação incerta ou incorreta de modo a não perder o estado anterior.
Veja nos testes como este padrão de projeto se comporta:
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Tests\Behavioral\Memento;
use Growthdev\DesignPatterns\Behavioral\Memento\Editor;
use Growthdev\DesignPatterns\Behavioral\Memento\History;
use PHPUnit\Framework\TestCase;
final class MementoTest extends TestCase
{
public function testMemento()
{
$editor = new Editor();
$history = new History();
$editor->setContent('I');
$history->save($editor->save());
$editor->setContent('I am');
$history->save($editor->save());
$editor->setContent('I am Walmir');
$history->save($editor->save());
$this->assertEquals('I am Walmir', $editor->getContent());
$history->restore($editor);
$this->assertEquals('I am', $editor->getContent());
$history->restore($editor);
$this->assertEquals('I', $editor->getContent());
}
}
Um cuidado que você tem que ter ao utilizar este pattern é que código de uma classe, pode pelo fato de ficar distribuído em outras, pode aumentar a complexidade da solução. De resto, é um bom pattern!
No artigo Resumo dos Padrões de Projetos você encontrará outros patterns com exemplos. E todos os códigos estão disponíveis no meu github:
https://github.com/growthdev-repo/design-patterns
Espero que você esteja curtindo estes artigos. Se gostou, não deixe de comentar e compartilhar! Até o próximo artigo!
Confiança Sempre!!!
Fontes:

Seja o primeiro a comentar