Entendendo/Conhecendo o padrão Memento

Posted on junho 11, 2011 by

1


Olá pessoal, como estamos?

Essa semana foi muito bacana. Muito trabalho, muita discussão, além de toda repercussão do #DNAD11. Em função de tudo isso, acabou havendo bem pouca atividade aqui no blog. Entretanto, acho que partindo de agora, as coisas começam a voltar para seu estado normal.

Minha proposta de hoje é falar um pouco sobre um pattern muito útil, mas, infelizmente, pouco conhecido. Hoje pretendo tratar um pouco do padrão Memento.

A motivação desse post surgiu durante uma palestra do no #DNAD11. Na ocasião, ele perguntou quem conhecia esse pattern e, para minha surpresa, bem poucas mãos foram levantadas. Vejamos se podemos remediar isso.

O que é o padrão Memento?

Memento pode ser classificado como um “Behavioral Pattern” e está relacionado entre os padrões apresentados pelo GoF. Sua função é: capturar e externalizar o estado interno de um objeto para que esse possa ser restaurado mais tarde, sem violar o encapsulamento.

Planejando Memento

O padrão Memento tem um design muito simples e, ao meu ver, é muito elegante. Considere o seguinte diagrama UML:

image

Vamos tentar entender um pouco mais as responsabilidades dos “elementos” presentes nesse design…

O objeto Originator é o nosso “target” – Trata-se do objeto que deverá ter seu estado preservado e restaurado.  O objeto Memento é o responsável por preservar o estado do objeto Originator. Por fim, o objeto Caretaker orquestra as atividades  do Originator e a manutenção de Mementos.

Analisando cada “papel” com pouco mais de cuidado, podemos dizer também:

  • Memento
    • armazena o estado interno do objeto Originator. ´
    • é importante que o Memento salve o mínimo possível do estado do Originator.
    • ele deve restringir acesso aos dados, da melhor forma possível,  para qualquer outro objeto que não seja o Originator. Assim, Mementos precisam ter duas interfaces. Uma para Caretaker – restrita a permitir armazenamento. Outra para Originator, de forma que esse possa ter acesso a todos os dados necessários para restaurar dados.
  • Originator (SalesProspect)
    • cria um memento contendo detalhes de seu estado (interno);
    • utilizando o memento, tem condições de restaurar seu estado interno.
  • Caretaker (Caretaker)
    • persiste e gerencia a manutenção de um memento;
    • nunca tenta acessar as informações armazenadas pelo memento.

Implementação simples

Perfeito! Agora que já entendemos a dinâmica do padrão, vejamos uma implementação simples. Tudo começa com o tipo que desejamos “armazenar”. Mantendo a terminologia utilizada acima, o Originator.


              
public partial class Foo
{
    string PropAField;
    public string PropA
    {
        get { return PropAField; }
        set { PropAField = value; }
    }

    int PropBField;
    public int PropB
    {
        get { return PropBField; }
        set { PropBField = value; }
    }
}

            

Optei por uma classe simples aqui. Entretanto, geralmente, memento fica mais interessante em implementações de máquinas de estado (tema para outro post).

Marquei a classe como partial para facilitar a “organização” do código. Dessa forma, posso colocar a lógica do memento em outro arquivo. Observe:


              
public partial class Foo
{
    public FooMemento SaveToMemento()
    { return new FooMemento(this); }

    public void RestoreFromMemento(FooMemento memento)
    { memento.Restore(this); }

    public class FooMemento
    {
        internal FooMemento(Foo foo)
        {
            propA = foo.PropAField;
            propB = foo.PropBField;
        }

        internal void Restore(Foo foo)
        {
            foo.PropAField = propA;
            foo.PropBField = propB;
        }

        string propA;
        int propB;
    }
}

            

Algumas considerações:

  • Deixei o memento como classe “aninhada” do Originator. Assim, permito ao memento “ver os atributos private”;
  • Salvo e recupero o estado do Originator dentro do Memento. Assim, garanto que o estado fica completamente “fechado” de qualquer objeto externo (Caretaker, inclusive);
  • Mantive o construtor do memento como internal.

 

Pensando em um Memento mais “genérico”

Não gosto muito de generalizações. Aqui não será diferente. Entretanto, há quem goste, então lá vai. Considere o seguinte Memento:


              
class Memento
{
    MemoryStream _stream = new MemoryStream();
    SoapFormatter _formatter = new SoapFormatter();

    public Memento(T o)
    {
        _formatter.Serialize(_stream, o);
    }

    public T Restore()
    {
        _stream.Seek(0, SeekOrigin.Begin);
        object o = _formatter.Deserialize(_stream);
        _stream.Close();

        return (T) o;
    }
}

            

Visivelmente, esse Memento é uma alternativa mais abrangente e menos trabalhosa. Entretanto, perceba como ele aumenta o acoplamento com o Framework (novas referências são necessárias). Ele também dispensa a necessidade de alterar a interface pública do Originator mas exige que a classe seja marcada como Serializable.

Por hoje, era isso.

Smiley piscando

Posted in: C#, DesignPatterns