Padrões GoF Comportamentais Avançados: State, Memento, Visitor e Template Method
Como estruturar comportamentos complexos e mutabilidade em software corporativo: uma análise profunda sobre State, Memento, Visitor, Template Method e a gestão de estados Stateful versus Stateless.
No catálogo clássico do Gang of Four (GoF), os padrões comportamentais lidam não apenas com como as classes e objetos são estruturados, mas fundamentalmente com os algoritmos de comunicação e a atribuição de responsabilidades operacionais em tempo de execução. Enquanto padrões populares como Strategy e Observer solucionam variações de algoritmos e notificações de eventos, sistemas corporativos complexos frequentemente enfrentam desafios mais profundos: mutabilidade de ciclo de vida com dezenas de transições condicionais, reversão segura de snapshots em memória (operações de undo/rollback), travessia de estruturas heterogêneas sem poluição de domínio e esqueletos de algoritmos extensíveis.
Neste artigo, desmistificamos os padrões comportamentais mais sofisticados da engenharia orientada a objetos — State, Memento, Visitor, Template Method, além de Iterator e Interpreter —, contrastando as arquiteturas de execução Stateful e Stateless.
Stateful vs. Stateless: O Dilema da Preservação de Estado
Antes de selecionar um padrão de comportamento, o arquiteto precisa definir onde reside o estado da aplicação:
| Característica | Arquitetura Stateful | Arquitetura Stateless |
|---|---|---|
| Retenção de Dados | O componente mantém variáveis de contexto e histórico entre invocações sucessivas. | Cada invocação é isolada; dados necessários são recebidos no payload ou buscados externamente. |
| Escalabilidade Horizontal | Exige sessões distribuídas, sticky sessions ou afinidade de nó no balanceador. | Trivial; qualquer réplica de container ou worker pode processar qualquer requisição. |
| Complexidade de Recuperação | Falhas na instância exigem réplicas de sincronização de memória ou perda de sessão. | Resiliente; falhas de nós não afetam requisições subsequentes dos clientes. |
| Padrões de Software Alinhados | State, Memento, Finite State Machines (FSM), atores distribuídos. | Strategy, Command, lambdas puras, microsserviços REST e funções serverless. |
Em microsserviços modernos, a camada de transporte e de APIs adota primariamente o modelo Stateless, delegando a retenção de estado a bancos distribuídos ou caches centralizados. No entanto, dentro da camada de domínio (no modelo de agregados de DDD ou em motores de processamento de fluxo), os objetos em execução em memória operam sob o padrão Stateful enquanto executam transações críticas de negócio.
O Padrão State: Máquinas de Estado Limpas e Sem switch/case
O padrão State permite que um objeto altere seu comportamento quando seu estado interno muda. O objeto parecerá ter mudado de classe.
Quando um modelo acumula dezenas de validações condicionais baseadas em status (if (status == Draft) { ... } else if (status == InReview) { ... }), o código sofre de fragilidade extrema: qualquer novo estado exige a reedição de todos os métodos existentes, violando o princípio Aberto/Fechado (OCP).
stateDiagram-v2
[*] --> Rascunho
Rascunho --> EmRevisao: Submeter para Revisão
EmRevisao --> Aprovado: Aprovar
EmRevisao --> Rejeitado: Solicitar Correções
Rejeitado --> Rascunho: Editar Conteúdo
Aprovado --> Publicado: Publicar Imediato
Publicado --> Arquivado: Expirar Vigência
Arquivado --> [*]
Implementação Canônica em TypeScript
Em vez de verificar primitivas, o contexto delega todas as ações dependentes do ciclo de vida para a interface polimórfica DocumentState:
// Interface de estado
export interface DocumentState {
edit(context: DocumentContext, content: string): void;
publish(context: DocumentContext): void;
archive(context: DocumentContext): void;
}
// Contexto que mantém a referência do estado ativo
export class DocumentContext {
private state: DocumentState;
private content: string = "";
constructor(initialState: DocumentState) {
this.state = initialState;
}
public transitionTo(state: DocumentState): void {
console.log(`Transição de estado para: ${state.constructor.name}`);
this.state = state;
}
public setContent(text: string): void {
this.content = text;
}
public getContent(): string {
return this.content;
}
public edit(newContent: string): void {
this.state.edit(this, newContent);
}
public publish(): void {
this.state.publish(this);
}
public archive(): void {
this.state.archive(this);
}
}
// Estados concretos
export class DraftState implements DocumentState {
edit(context: DocumentContext, content: string): void {
context.setContent(content);
console.log("Documento atualizado no estado Rascunho.");
}
publish(context: DocumentContext): void {
console.log("Rascunho submetido e aprovado diretamente para publicação.");
context.transitionTo(new PublishedState());
}
archive(context: DocumentContext): void {
throw new Error("Rascunhos não podem ser arquivados diretamente. Cancele ou descarte.");
}
}
export class PublishedState implements DocumentState {
edit(context: DocumentContext, content: string): void {
throw new Error("Não é permitido editar documento já publicado. Crie uma nova revisão.");
}
publish(context: DocumentContext): void {
console.log("O documento já se encontra publicado.");
}
archive(context: DocumentContext): void {
console.log("Arquivando documento publicado.");
context.transitionTo(new ArchivedState());
}
}
export class ArchivedState implements DocumentState {
edit(context: DocumentContext, content: string): void {
throw new Error("Documento arquivado é somente leitura perpétuo.");
}
publish(context: DocumentContext): void {
throw new Error("Documentos arquivados não podem ser republicados sem desarquivamento explícito.");
}
archive(context: DocumentContext): void {
console.log("Documento já está arquivado.");
}
}
O Padrão Memento: Snapshots e Operações de Desfazer (Undo)
O padrão Memento captura e externaliza o estado interno de um objeto sem violar o encapsulamento, permitindo que o objeto seja restaurado para esse estado posteriormente.
O Memento resolve um problema clássico de arquitetura: se tornarmos os campos internos de uma classe públicos para que um gerenciador de histórico salve snapshots, destruímos o encapsulamento. O Memento utiliza uma estrutura de três agentes:
- Originator (Originador): A classe que possui o estado crítico e sabe como criar e restaurar o snapshot.
- Memento: O recipiente opaco que transporta o estado capturado.
- Caretaker (Zelador / Histórico): Armazena a pilha de mementos sem jamais inspecionar ou manipular os dados internos salvos.
sequenceDiagram
autonumber
actor Dev as Cliente / Editor
participant O as Editor (Originator)
participant C as Historico (Caretaker)
participant M as Memento (Snapshot)
Dev->>O: digitarTexto("Versão 1")
Dev->>O: criarSnapshot()
O-->>M: new EditorMemento(texto, cursor)
O-->>Dev: memento
Dev->>C: salvar(memento)
Dev->>O: digitarTexto("Alteração incorreta")
Dev->>C: desfazer()
C-->>Dev: ultimoMemento
Dev->>O: restaurar(ultimoMemento)
O->>M: lerEstadoInterno()
Note over O: Estado restaurado para "Versão 1" sem quebrar encapsulamento
O Padrão Visitor: Adicionando Operações sem Modificar Classes
O padrão Visitor permite definir uma nova operação sobre uma estrutura de elementos heterogêneos sem mudar as classes dos elementos sobre os quais opera. Ele implementa o mecanismo de Double Dispatch:
flowchart LR
subgraph Elementos["Estrutura de Elementos (Composite / AST)"]
E1["Parágrafo"]
E2["Tabela"]
E3["Bloco de Código"]
end
subgraph Visitantes["Visitantes Polimórficos"]
V1["HtmlExporterVisitor<br/>.visitParagraph()<br/>.visitTable()<br/>.visitCodeBlock()"]
V2["MarkdownExporterVisitor<br/>.visitParagraph()<br/>.visitTable()<br/>.visitCodeBlock()"]
V3["WordCountVisitor<br/>.visitParagraph()<br/>.visitTable()<br/>.visitCodeBlock()"]
end
Elementos -->|accept visitor| Visitantes
Exemplo de Double Dispatch em C#
public interface IDocumentElement
{
void Accept(IDocumentVisitor visitor);
}
public interface IDocumentVisitor
{
void Visit(ParagraphElement paragraph);
void Visit(TableElement table);
}
public class ParagraphElement : IDocumentElement
{
public string Text { get; set; } = string.Empty;
public void Accept(IDocumentVisitor visitor) => visitor.Visit(this);
}
public class TableElement : IDocumentElement
{
public List<List<string>> Rows { get; set; } = new();
public void Accept(IDocumentVisitor visitor) => visitor.Visit(this);
}
// O visitante encapsula toda a lógica de exportação sem poluir as classes de dados
public class MarkdownExportVisitor : IDocumentVisitor
{
public StringBuilder Output { get; } = new();
public void Visit(ParagraphElement paragraph)
{
Output.AppendLine(paragraph.Text).AppendLine();
}
public void Visit(TableElement table)
{
foreach (var row in table.Rows)
{
Output.AppendLine("| " + string.Join(" | ", row) + " |");
}
Output.AppendLine();
}
}
O Padrão Template Method: Esqueletos de Algoritmos Invariantes
O padrão Template Method define o esqueleto de um algoritmo no método de uma superclasse, postergando a definição de alguns passos específicos para as subclasses. O Template Method preserva a estrutura invariante do fluxo enquanto fornece ganchos (hooks) de especialização:
flowchart TD
A["Método Template: processarPipeline()"] --> B["1. extrairPayload() (Abstrato)"]
B --> C["2. autenticarRequisicao() (Comum / Concreto)"]
C --> D["3. transformarDados() (Abstrato)"]
D --> E["4. hookAuditoria() (Opcional / Gancho)"]
E --> F["5. persistirResultado() (Abstrato)"]
Diferença crucial com o padrão Strategy:
- Template Method baseia-se em herança de classes em tempo de compilação; o esqueleto do algoritmo é fixo na classe base e as subclasses apenas preenchem etapas.
- Strategy baseia-se em composição de objetos em tempo de execução; o algoritmo inteiro é delegado dinamicamente a uma instância de estratégia intercambiável.
Outros Padrões Comportamentais do GoF
Além dos quatro núcleos analisados, o catálogo clássico contém:
- Iterator: Fornece uma maneira de acessar sequencialmente os elementos de um objeto agregado sem expor sua representação subjacente (implementado nativamente em geradores e
IEnumerable/Symbol.iterator). - Interpreter: Dada uma linguagem, define uma representação para sua gramática juntamente com um interpretador que usa a representação para interpretar sentenças na linguagem (comumente usado em DSLs simples e avaliadores de expressões matemáticas).
Matriz de Seleção dos Padrões Comportamentais
| Padrão | Problema Primário | Solução Estrutural | Quando Evitar |
|---|---|---|---|
| State | O objeto se comporta de maneiras radicalmente diferentes dependendo de seu estado e possui muitas condicionais. | Cada estado vira uma classe com métodos uniformes; o contexto delega operações ao estado ativo. | Máquinas de estado muito triviais (ex.: apenas dois estados booleanos ativo/inativo). |
| Memento | Necessidade de salvar e desfazer o estado de um objeto complexo sem expor seus membros privados. | Criação de mementos opacos gerenciados por um histórico externo. | Estados com uso massivo de memória RAM sem compressão ou clonagem estrutural eficiente. |
| Visitor | Executar novas operações em árvores hierárquicas complexas (ASTs, composites) sem alterar suas classes. | Métodos polimórficos de recepção (Accept) e despacho duplo para classes visitantes. |
Estruturas de classes que sofrem alterações frequentes de nós, pois todo visitante é forçado a mudar. |
| Template Method | Vários algoritmos compartilham os mesmos passos e estrutura geral, diferindo apenas em detalhes específicos. | Superclasse com método esqueleto final chamando métodos abstratos ou ganchos virtuais. | Quando a composição for mais flexível que herança rígida; prefira Strategy para inversão limpa. |
Conclusão
Os padrões comportamentais do GoF são ferramentas de precisão arquitetural. Ao substituir condicionais de ciclo de vida por State, preservar integridade de dados durante rollbacks com Memento, isolar novas operações de formatação com Visitor e padronizar esqueletos de processamento com Template Method, você transforma bases de código monolíticas e imperativas em ecossistemas desacoplados, auditáveis e sustentáveis a longo prazo.