Pular para o conteúdo
GoF/23 Padrões de Projeto Comportamentais
305
Comportamental escopo de objeto também: Objects for States

State

Estado

O objeto parece mudar de classe quando seu estado interno muda.

Complexidade média Frequência de uso moderada
D

Definição

Intenção — palavra por palavra, como o livro define

Permitir que um objeto altere seu comportamento quando seu estado interno muda. O objeto parecerá ter mudado de classe.

E

Exemplo e motivação

Analogia

O botão do celular

O mesmo botão lateral faz coisas diferentes: com a tela desbloqueada, apaga a tela; com a tela apagada, acende; durante uma chamada, encerra. Não é o botão que mudou — é o estado do aparelho que redefine o significado da mesma ação.

O problema

Um objeto se comporta de formas diferentes dependendo do seu estado, e cada método vira um `switch (this.estado)` gigante. Adicionar um estado obriga a mexer em todos os métodos, e as transições válidas ficam implícitas e espalhadas — bugs de “pedido entregue foi cancelado” nascem aí.

A solução

Crie uma classe por estado, cada uma implementando os métodos do jeito daquele estado. O objeto original (Context) guarda uma referência ao objeto de estado atual e delega tudo a ele. Para transitar, troca-se o objeto de estado. As transições válidas ficam explícitas no código.

Sintomascomo reconhecer no seu código
  • switch (pedido.status) repetido em cinco métodos.
  • Bugs de transição ilegal em produção.
Na práticaonde ele já existe
  • javax.faces.lifecycle.LifeCycle
  • Máquinas de estado de workflow (XState, Spring State Machine)
  • Estados de conexão TCP; ciclo de vida de threads e de processos
D

Diagrama

Pedido (Context)- estado+ pagar()+ transitar(s)«interface»EstadoDoPedido+ pagar()+ enviar()+ cancelar()AguardandoPagoEntreguedelegatransita
Fig. 20 — State herda / implementa cria usa / contém
Ver este diagrama sendo desenhado, traço a traço
Participantes
Context
Mantém a referência ao estado atual e delega o trabalho a ele.
State
Interface com as operações dependentes de estado.
ConcreteState
Implementa o comportamento de um estado e dispara as transições permitidas.
A

Aplicabilidade

Use quando
  • Um objeto se comporta de forma muito diferente conforme o estado, e os estados mudam com frequência.
  • Há condicionais enormes e duplicadas sobre o valor de um campo de estado.
  • Existem transições ilegais que precisam ser impedidas.
Evite quando
  • São dois estados com pouca lógica — um booleano resolve.
C

Consequências

A favor
  • Cada estado numa classe (Responsabilidade Única).
  • Novos estados entram sem alterar os existentes nem o contexto (Aberto/Fechado).
  • Elimina condicionais gigantes de máquina de estado.
  • Torna as transições válidas explícitas e verificáveis.
Contra
  • Exagero quando há poucos estados ou eles mudam raramente.
  • Mais classes para navegar.
Apêndice

Código

Listagem 20 Ciclo de vida de um pedido com transições explícitas
// ── State: cada operação, uma vez por estado ──────────
class EstadoPedido {
  constructor(pedido) { this.pedido = pedido; }
  get nome()  { return this.constructor.name; }
  pagar()     { return this.#invalido('pagar'); }
  enviar()    { return this.#invalido('enviar'); }
  entregar()  { return this.#invalido('entregar'); }
  cancelar()  { return this.#invalido('cancelar'); }
  #invalido(acao) { throw new Error(`❌ não é possível "${acao}" em ${this.nome}`); }
}

class Aguardando extends EstadoPedido {
  pagar()    { this.pedido.transitar(new Pago(this.pedido));      return '💳 pagamento aprovado'; }
  cancelar() { this.pedido.transitar(new Cancelado(this.pedido)); return '🚫 pedido cancelado'; }
}
class Pago extends EstadoPedido {
  enviar()   { this.pedido.transitar(new Enviado(this.pedido));   return '📦 despachado'; }
  cancelar() { this.pedido.transitar(new Cancelado(this.pedido)); return '🚫 cancelado com estorno'; }
}
class Enviado extends EstadoPedido {
  entregar() { this.pedido.transitar(new Entregue(this.pedido));  return '✅ entregue'; }
}
class Entregue  extends EstadoPedido {}   // estado final: tudo herda "inválido"
class Cancelado extends EstadoPedido {}

// ── Context: fino, apenas delega ──────────────────────
class Pedido {
  constructor() { this.estado = new Aguardando(this); this.trilha = ['Aguardando']; }
  transitar(novo) { this.estado = novo; this.trilha.push(novo.nome); }

  pagar()    { return this.estado.pagar(); }
  enviar()   { return this.estado.enviar(); }
  entregar() { return this.estado.entregar(); }
  cancelar() { return this.estado.cancelar(); }
}

const p = new Pedido();
console.log(p.pagar());      // 💳 pagamento aprovado
console.log(p.enviar());     // 📦 despachado
console.log(p.entregar());   // ✅ entregue
try { p.cancelar(); } catch (e) { console.log(e.message); }
// ❌ não é possível "cancelar" em Entregue   ← a regra é estrutural, não um if esquecido
console.log(p.trilha.join(' → '));
Apêndice

Relações

parecido / fácil de confundir
Strategy
Estrutura idêntica. No State, os estados CONHECEM uns aos outros e disparam transições; nas Strategies, elas se ignoram e quem escolhe é o cliente.
Bridge
Mesma estrutura, intenção diferente.
costuma andar junto
Flyweight
Estados sem campos próprios podem ser compartilhados entre contextos.
Singleton
Estados imutáveis costumam ser instâncias únicas.
Apêndice

Verificação

Questãoa resposta está marcada

A distinção mais citada entre State e Strategy é:

  1. Nos estados, os objetos conhecem uns aos outros e comandam a transição; estratégias são independentes e escolhidas pelo cliente
  2. State usa herança; Strategy usa composição
  3. Strategy não pode ser trocada em tempo de execução
  4. State só se aplica a objetos persistidos

A frase de Refactoring Guru resume: “State pode ser visto como uma extensão de Strategy” — com a diferença de que os estados sabem para onde podem ir.