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:
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.
Alberto Monteiro
junho 11, 2011
Esse padrão ai é bem bacana, depois que vi falar no DNAD fui pesquisar, ainda não implementei, mas farei isso logo logo ^^