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