[Delphi] Design Patterns GoF – Memento

Saudações, caros leitores!
Como bons programadores, sabemos que, em muitas ocasiões, é preciso salvar o estado atual de um objeto ou entidade antes de realizar uma operação, de forma que possamos restaurá-lo caso necessário. Um bom exemplo é o controle de transação de banco de dados – quando um erro ocorre, executamos um rollback na operação, restaurando o estado anterior de uma tabela. Esse procedimento de armazenamento e restauração de estados é basicamente o objetivo do Design Pattern deste artigo, chamado Memento.

Introdução

Ao pesquisar por “Memento” no Google, os primeiros resultados trazem informações de um filme de 2000, cujo tradução, em português, é “Amnésia”. O padrão de projeto Memento tem relação com esse significado. Quando alteramos o estado interno de um objeto (como os seus atributos), “perdemos” os valores armazenados anteriormente. No contexto deste artigo, podemos dizer que “esquecemos” dos valores anteriores. Daí a Amnésia.

Porém, a proposta do Memento é justamente o inverso: permitir que a aplicação se lembre dos estados anteriores de um objeto. Quando digo “lembrar”, me refiro especificamente à possibilidade de restaurar um estado anterior. Em termos mais técnicos, compare o Memento com a operação de desfazer (Ctrl + Z), utilizada com bastante frequência no dia-a-dia. Ao desfazer uma ação, o sistema restaura o estado anterior dos dados, ou seja, antes da última alteração.

O rollback de uma transação no banco de dados, mencionado no enunciado do artigo, também é um exemplo tradicional de restauração de um estado anterior que, neste caso, são os dados de tabelas.

Para complementar os exemplos, eu diria que o próprio Delphi utiliza uma implementação de Memento na funcionalidade de histórico dos arquivos. Cada vez que um arquivo de código é salvo com uma alteração, um novo backup é criado no subdiretório __history na pasta do projeto. Esse histórico de alterações pode ser visualizado na aba History do editor de códigos, permitindo que um estado anterior do arquivo seja restaurado. Agora ficou claro, não? 🙂

Aba History do editor de códigos exibindo os backups do arquivo

Exemplo de codificação do Memento

A compreensão do padrão de projeto ficará ainda mais fácil com um cenário prático.

Considere um editor de textos, bem simples, que contém um TEdit para o título, um TRichEdit para o texto e uma barra de ferramentas para formatação, exemplificado na imagem abaixo:

Editor de textos para exemplificar a aplicação do Design Pattern Memento

Do lado esquerdo do formulário, em um TListBox, armazenaremos o histórico das alterações do texto, no qual será salvo a cada ação do botão “Salvar Alterações”. Por meio deste histórico, poderemos navegar nos estados anteriores do texto, restaurando-os quando desejado, semelhante à funcionalidade que existe atualmente no WordPress.

Explicarei os elementos do Memento conforme avançamos nas codificações.

Classe Memento

O primeiro passo é modelar a classe que leva o próprio nome do padrão de projeto: Memento. Essa classe é responsável por armazenar os atributos do objeto que poderá ser restaurado. Sendo assim, cada instância dessa classe será um estado armazenado, ou, tecnicamente falando, um snapshot do objeto naquele momento. Para o nosso exemplo, haverá apenas dois atributos, chamados “Titulo” e “Texto”:

André, não devemos salvar também as formatações do texto?
Não, não precisa. Como bônus, apresentarei uma forma de extrair as formatações do TRichEdit como string. Por este motivo que a propriedade Texto foi declarada como WideString.

Classe Originator

O segundo passo é criar a classe Originator que, como o próprio nome infere, tem a função de “originar” os estados do objeto. Além disso, o a classe também disponibiliza um método para salvar o estado atual (título e texto com as formatações) e restaurar um estado anterior (sobrescrevendo o título e o texto existente).

O primeiro método cria uma instância do Memento, preenche e o retorna ao chamador. O segundo recebe um Memento como parâmetro para consumir os seus atributos. Podemos afirmar, portanto, que a instância do Originator sempre representará os dados vigentes do objeto.

Classe Caretaker

Algo está faltando, não acha? Precisamos de uma classe que contenha uma lista dos estados do objeto para coordenar os armazenamentos e as restaurações. Essa é responsabilidade do elemento Caretaker, que significa “zelador” em português. O Caretaker possui métodos para adicionar um novo estado e obter um estado já existente, lembrando que, quando menciono “estado”, me refiro à uma instância do Memento.

Para armazenar a lista de Mementos, considerei o uso da classe TObjectDictionary, do namespace System.Generics.Collections. Essa classe é bem similar ao TDictionary tradicional, porém, é exclusiva para armazenar objetos como valores. No construtor do TObjectDictionary, podemos indicar que o próprio dicionário será responsável por liberar os objetos armazenados, informando o valor [doOwnsValues].

Por fim, trabalharemos com uma combinação de data e hora como chave do dicionário. O objetivo é gravar um snapshot do texto na data e hora em que o usuário clicou no botão “Salvar Alterações”. Isso nos permitirá visualizar e identificar as alterações com mais facilidade.

Bom, feito isso, a codificação do padrão de projeto está concluída! O último passo é consumir toda essa arquitetura.

Em ação!

No formulário (editor de textos), teremos duas variáveis de classe: uma para o Originator, que irá refletir o estado atual do texto; e outra para o Caretaker, responsável por controlar os Mementos:

Para isso, claro, devemos criá-los no construtor, bem como destruí-los no destrutor:

Em seguida, acompanhem a codificação do botão “Salvar Alterações” e atentem-se aos comentários. Nesse método, conforme prometi que apresentaria no artigo, faço uso da classe TStringStream para obter o texto formatado do TRichEdit como string por meio da propriedade DataString.

Sem muito segredo, não é? Vejam que, após preencher os atributos do Originator, adicionamos um instantâneo do seu estado na lista do Caretaker através do método SalvarEstado. Lembram-se do que ele faz? Cria, preenche e retorna uma instância da classe Memento. 🙂

Cada vez que o botão “Salvar Alterações” for acionado, um novo item será adicionado na TListBox, indicando que um novo estado (Memento) foi armazenado na lista do Caretaker.

Imagine, agora, que o usuário aplicou algumas alterações incorretas no texto e deseja restaurar um estado anterior. O procedimento é simples. Codificaremos o evento OnClick da TListBox para buscar o estado (Memento) referente àquela data e hora selecionada, atribuindo suas propriedades ao Originator:

Na prática, este será o resultado:

Exemplo de editor de textos utilizando o Design Pattern Memento

Conclusão

Antes de finalizar o artigo, uma observação: no formulário (Client), não foi necessário adicionar a unit da classe Memento na seção uses, apenas do Originator e do Caretaker. Lembrem-se: baixo acoplamento!

Utilize o Memento em funcionalidades que exijam a recuperação de dados anteriores de um objeto específico. Uma ideia é utilizá-lo no preview de telas ou personalização de relatórios, permitindo que o usuário desfaça as alterações de forma rápida e confiável.

O projeto de exemplo deste artigo está disponível no link abaixo:

 

Agradeço pela atenção, leitores!
Até a próxima!


 

André Celestino