Ir ao conteúdo

Padrão de Projeto Memento em PHP com exemplo

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.

Diagrama de Classe UML do Padrão de Projeto Memento
Diagrama de Classe UML do Padrão de Projeto Memento

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.

Representação do Padrão Memento
Representação do Padrão Memento
<?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:

Publicado emDesign PatternPadrões de Projetos

Seja o primeiro a comentar

Deixe um comentário

O seu endereço de e-mail não será publicado.