State
Permite que um objeto altere seu comportamento quando seu estado interno muda — como se o objeto mudasse de classe em tempo de execução. Cada estado possível é encapsulado em sua própria classe, e as transições fazem parte do design.
Intenção
Encapsular cada estado possível de um objeto em sua própria classe e delegar o comportamento dependente de estado ao objeto de estado corrente. Do ponto de vista do contexto, o objeto parece "mudar de classe" quando muda de estado — mas na realidade ele troca o objeto de estado que detém o comportamento.
Catalogado pelo GoF (1994) como padrão comportamental, o State é a solução
elegante para máquinas de estado finitas: substitui condicionais extensas
(switch ou if/else) que verificam o estado atual
por uma estrutura onde cada estado conhece seus próprios comportamentos válidos
e suas transições permitidas.
Problema
Considere o workflow de um documento editorial: rascunho, em revisão e publicado. Cada estado permite ações diferentes: um rascunho pode ser enviado para revisão, mas não publicado diretamente; um documento em revisão pode ser aprovado (vai para publicado) ou rejeitado (volta para rascunho); um documento publicado não pode retroceder.
A abordagem ingênua codifica todas essas regras em condicionais dentro do contexto:
// Abordagem ingênua — NÃO faça isso:
class Documento {
private estado: string = "rascunho";
publicar(): void {
if (this.estado === "rascunho") {
throw new Error("Rascunho nao pode ser publicado diretamente.");
} else if (this.estado === "revisao") {
this.estado = "publicado";
console.log("Documento publicado.");
} else if (this.estado === "publicado") {
throw new Error("Ja publicado.");
}
}
enviarParaRevisao(): void {
if (this.estado === "rascunho") {
this.estado = "revisao";
} else {
throw new Error("So rascunhos podem ir para revisao.");
}
}
// Cada novo estado ou ação cresce todos os if/else acima.
}
Adicionar um novo estado (ex.: arquivado) exige abrir todos os métodos e adicionar mais uma ramificação — violação do Princípio Aberto/Fechado. O State resolve isso extraindo cada estado para sua própria classe.
Solução
O State organiza o código em três participantes:
-
Context: mantém uma referência ao estado atual
(
EstadoDocumento) e delega as operações a ele. Expõe um método interno para que os estados possam solicitar a troca de estado. - State (interface): declara os métodos que representam as operações dependentes de estado. Todos os estados concretos implementam esta interface.
- ConcreteState: implementa o comportamento correspondente a um estado específico do contexto e pode disparar transições chamando o método de troca de estado no contexto.
Distinção-chave: State vs Strategy
State e Strategy são estruturalmente idênticos — ambos têm um contexto que delega para um objeto intercambiável que implementa uma interface comum. A diferença está na intenção e no controle das transições:
- Strategy: o cliente escolhe e injeta o algoritmo. O contexto não sabe qual estratégia está ativa e não se importa — não há transições automáticas. Algoritmos não se conhecem entre si.
- State: as transições são parte do design e acontecem automaticamente. Os estados se conhecem: um estado sabe para quais outros estados pode transitar. O cliente raramente troca o estado diretamente — o estado atual faz isso em resposta a eventos.
Estrutura
«interface»
EstadoDocumento
┌──────────────────────────────────────┐
│ + publicar(ctx: Documento): void │
│ + enviarParaRevisao(ctx): void │
│ + rejeitar(ctx: Documento): void │
└──────────────────────────────────────┘
▲
┌──────────┼──────────────────────┐
│ │ │
EstadoRascunho EstadoRevisao EstadoPublicado
(ConcreteState) (ConcreteState) (ConcreteState)
enviar OK publicar OK todas: erro
publicar err rejeitar OK
Documento (Context)
┌──────────────────────────────────────┐
│ - estado: EstadoDocumento │
│ + publicar(): void │
│ + enviarParaRevisao(): void │
│ + rejeitar(): void │
│ + transicionarPara(e): void [pkg] │
└──────────────────────────────────────┘
│ delega para
▼
EstadoDocumento (corrente)
Fluxo de transição:
doc.enviarParaRevisao()
→ EstadoRascunho.enviarParaRevisao(doc)
→ doc.transicionarPara(new EstadoRevisao())
doc.publicar()
→ EstadoRevisao.publicar(doc)
→ doc.transicionarPara(new EstadoPublicado())
Exemplos de código
Exemplo 1 — Workflow de documento (rascunho → revisão → publicado)
Implementação completa com interface de estado, estados concretos que controlam as próprias transições e um contexto que delega sem condicionais.
// ── State interface ───────────────────────────────────────────
interface EstadoDocumento {
publicar(doc: Documento): void;
enviarParaRevisao(doc: Documento): void;
rejeitar(doc: Documento): void;
nome(): string;
}
// ── Context ───────────────────────────────────────────────────
class Documento {
private estado: EstadoDocumento;
constructor(private readonly titulo: string) {
this.estado = new EstadoRascunho();
}
// Chamado pelos estados concretos — não pelo cliente diretamente.
transicionarPara(novoEstado: EstadoDocumento): void {
console.log(` [${this.titulo}] ${this.estado.nome()} -> ${novoEstado.nome()}`);
this.estado = novoEstado;
}
publicar(): void { this.estado.publicar(this); }
enviarParaRevisao(): void { this.estado.enviarParaRevisao(this); }
rejeitar(): void { this.estado.rejeitar(this); }
getNomeEstado(): string { return this.estado.nome(); }
}
// ── ConcreteStates ────────────────────────────────────────────
class EstadoRascunho implements EstadoDocumento {
publicar(doc: Documento): void {
throw new Error("Rascunho nao pode ser publicado sem revisao.");
}
enviarParaRevisao(doc: Documento): void {
doc.transicionarPara(new EstadoRevisao());
}
rejeitar(doc: Documento): void {
throw new Error("Rascunho nao pode ser rejeitado.");
}
nome(): string { return "Rascunho"; }
}
class EstadoRevisao implements EstadoDocumento {
publicar(doc: Documento): void {
doc.transicionarPara(new EstadoPublicado());
}
enviarParaRevisao(doc: Documento): void {
throw new Error("Ja esta em revisao.");
}
rejeitar(doc: Documento): void {
doc.transicionarPara(new EstadoRascunho());
}
nome(): string { return "Em Revisao"; }
}
class EstadoPublicado implements EstadoDocumento {
publicar(doc: Documento): void {
throw new Error("Ja publicado.");
}
enviarParaRevisao(doc: Documento): void {
throw new Error("Documento publicado nao retorna para revisao.");
}
rejeitar(doc: Documento): void {
throw new Error("Documento publicado nao pode ser rejeitado.");
}
nome(): string { return "Publicado"; }
}
// ── Uso ──────────────────────────────────────────────────────
const doc = new Documento("Guia de Design Patterns");
doc.enviarParaRevisao();
// [Guia de Design Patterns] Rascunho -> Em Revisao
doc.rejeitar();
// [Guia de Design Patterns] Em Revisao -> Rascunho
doc.enviarParaRevisao();
// [Guia de Design Patterns] Rascunho -> Em Revisao
doc.publicar();
// [Guia de Design Patterns] Em Revisao -> Publicado
console.log("Estado final:", doc.getNomeEstado());
// Estado final: Publicado
try {
doc.publicar(); // ja publicado
} catch (e) {
console.log("Erro esperado:", (e as Error).message);
// Erro esperado: Ja publicado.
}
<?php
// ── State interface ───────────────────────────────────────────
interface EstadoDocumento
{
public function publicar(Documento $doc): void;
public function enviarParaRevisao(Documento $doc): void;
public function rejeitar(Documento $doc): void;
public function nome(): string;
}
// ── Context ───────────────────────────────────────────────────
class Documento
{
private EstadoDocumento $estado;
public function __construct(private readonly string $titulo)
{
$this->estado = new EstadoRascunho();
}
public function transicionarPara(EstadoDocumento $novoEstado): void
{
printf(" [%s] %s -> %s\n", $this->titulo, $this->estado->nome(), $novoEstado->nome());
$this->estado = $novoEstado;
}
public function publicar(): void { $this->estado->publicar($this); }
public function enviarParaRevisao(): void { $this->estado->enviarParaRevisao($this); }
public function rejeitar(): void { $this->estado->rejeitar($this); }
public function getNomeEstado(): string { return $this->estado->nome(); }
}
// ── ConcreteStates ────────────────────────────────────────────
class EstadoRascunho implements EstadoDocumento
{
public function publicar(Documento $doc): void
{
throw new \LogicException('Rascunho nao pode ser publicado sem revisao.');
}
public function enviarParaRevisao(Documento $doc): void
{
$doc->transicionarPara(new EstadoRevisao());
}
public function rejeitar(Documento $doc): void
{
throw new \LogicException('Rascunho nao pode ser rejeitado.');
}
public function nome(): string { return 'Rascunho'; }
}
class EstadoRevisao implements EstadoDocumento
{
public function publicar(Documento $doc): void
{
$doc->transicionarPara(new EstadoPublicado());
}
public function enviarParaRevisao(Documento $doc): void
{
throw new \LogicException('Ja esta em revisao.');
}
public function rejeitar(Documento $doc): void
{
$doc->transicionarPara(new EstadoRascunho());
}
public function nome(): string { return 'Em Revisao'; }
}
class EstadoPublicado implements EstadoDocumento
{
public function publicar(Documento $doc): void
{
throw new \LogicException('Ja publicado.');
}
public function enviarParaRevisao(Documento $doc): void
{
throw new \LogicException('Documento publicado nao retorna para revisao.');
}
public function rejeitar(Documento $doc): void
{
throw new \LogicException('Documento publicado nao pode ser rejeitado.');
}
public function nome(): string { return 'Publicado'; }
}
// ── Uso ──────────────────────────────────────────────────────
$doc = new Documento('Guia de Design Patterns');
$doc->enviarParaRevisao();
// [Guia de Design Patterns] Rascunho -> Em Revisao
$doc->rejeitar();
// [Guia de Design Patterns] Em Revisao -> Rascunho
$doc->enviarParaRevisao();
// [Guia de Design Patterns] Rascunho -> Em Revisao
$doc->publicar();
// [Guia de Design Patterns] Em Revisao -> Publicado
echo "Estado final: " . $doc->getNomeEstado() . "\n";
// Estado final: Publicado
try {
$doc->publicar();
} catch (\LogicException $e) {
echo "Erro esperado: " . $e->getMessage() . "\n";
// Erro esperado: Ja publicado.
}
Exemplo 2 — Singleton de estado: objetos de estado compartilhados
Quando os estados não têm dados próprios (são stateless), eles podem ser compartilhados como singletons — economizando alocações. Esta é uma otimização mencionada pelo GoF e ilustra a relação entre State e Singleton.
// Estados stateless podem ser compartilhados — instâncias únicas por tipo.
// Aqui usamos objetos literais (mais leve que classes em TS para casos simples).
interface EstadoPlayer {
play(player: MediaPlayer): void;
pause(player: MediaPlayer): void;
stop(player: MediaPlayer): void;
nome(): string;
}
// Estado singleton como objeto literal tipado.
const Parado: EstadoPlayer = {
play(player) { player.transicionarPara(Tocando); },
pause(_p) { /* ignorar — ja parado */ },
stop(_p) { /* ja parado */ },
nome() { return "Parado"; },
};
const Tocando: EstadoPlayer = {
play(_p) { /* ja tocando */ },
pause(player){ player.transicionarPara(Pausado); },
stop(player) { player.transicionarPara(Parado); },
nome() { return "Tocando"; },
};
const Pausado: EstadoPlayer = {
play(player) { player.transicionarPara(Tocando); },
pause(_p) { /* ja pausado */ },
stop(player) { player.transicionarPara(Parado); },
nome() { return "Pausado"; },
};
class MediaPlayer {
private estado: EstadoPlayer = Parado;
transicionarPara(novoEstado: EstadoPlayer): void {
console.log(` ${this.estado.nome()} -> ${novoEstado.nome()}`);
this.estado = novoEstado;
}
play(): void { this.estado.play(this); }
pause(): void { this.estado.pause(this); }
stop(): void { this.estado.stop(this); }
getEstado(): string { return this.estado.nome(); }
}
// ── Uso ──────────────────────────────────────────────────────
const player = new MediaPlayer();
console.log("Inicio:", player.getEstado()); // Inicio: Parado
player.play(); // Parado -> Tocando
player.pause(); // Tocando -> Pausado
player.play(); // Pausado -> Tocando
player.stop(); // Tocando -> Parado
console.log("Fim:", player.getEstado()); // Fim: Parado
<?php
// Estados stateless em PHP — classes com instância única (flyweight/singleton leve).
interface EstadoPlayer
{
public function play(MediaPlayer $player): void;
public function pause(MediaPlayer $player): void;
public function stop(MediaPlayer $player): void;
public function nome(): string;
}
class EstadoParado implements EstadoPlayer
{
private static ?self $instance = null;
private function __construct() {}
public static function getInstance(): self
{
return self::$instance ??= new self();
}
public function play(MediaPlayer $player): void { $player->transicionarPara(EstadoTocando::getInstance()); }
public function pause(MediaPlayer $player): void { /* ja parado — ignorar */ }
public function stop(MediaPlayer $player): void { /* ja parado */ }
public function nome(): string { return 'Parado'; }
}
class EstadoTocando implements EstadoPlayer
{
private static ?self $instance = null;
private function __construct() {}
public static function getInstance(): self
{
return self::$instance ??= new self();
}
public function play(MediaPlayer $player): void { /* ja tocando */ }
public function pause(MediaPlayer $player): void { $player->transicionarPara(EstadoPausado::getInstance()); }
public function stop(MediaPlayer $player): void { $player->transicionarPara(EstadoParado::getInstance()); }
public function nome(): string { return 'Tocando'; }
}
class EstadoPausado implements EstadoPlayer
{
private static ?self $instance = null;
private function __construct() {}
public static function getInstance(): self
{
return self::$instance ??= new self();
}
public function play(MediaPlayer $player): void { $player->transicionarPara(EstadoTocando::getInstance()); }
public function pause(MediaPlayer $player): void { /* ja pausado */ }
public function stop(MediaPlayer $player): void { $player->transicionarPara(EstadoParado::getInstance()); }
public function nome(): string { return 'Pausado'; }
}
class MediaPlayer
{
private EstadoPlayer $estado;
public function __construct()
{
$this->estado = EstadoParado::getInstance();
}
public function transicionarPara(EstadoPlayer $novoEstado): void
{
printf(" %s -> %s\n", $this->estado->nome(), $novoEstado->nome());
$this->estado = $novoEstado;
}
public function play(): void { $this->estado->play($this); }
public function pause(): void { $this->estado->pause($this); }
public function stop(): void { $this->estado->stop($this); }
public function getEstado(): string { return $this->estado->nome(); }
}
// ── Uso ──────────────────────────────────────────────────────
$player = new MediaPlayer();
echo "Inicio: " . $player->getEstado() . "\n"; // Inicio: Parado
$player->play(); // Parado -> Tocando
$player->pause(); // Tocando -> Pausado
$player->play(); // Pausado -> Tocando
$player->stop(); // Tocando -> Parado
echo "Fim: " . $player->getEstado() . "\n"; // Fim: Parado
Quando usar
- Quando um objeto muda radicalmente de comportamento dependendo do estado e há muitos estados possíveis: workflows (pedido, documento, ticket), players de mídia, conexões de rede (conectado/desconectado/reconectando), portas de comunicação.
- Quando as transições de estado são complexas e têm regras: cada estado sabe de quais outros estados pode vir e para quais pode ir. Isso fica claro e testável quando encapsulado em classes de estado.
-
Para eliminar condicionais de estado espalhadas por vários métodos:
se você tem
if (this.estado === "x")repetido em dezenas de métodos, State extrai essa lógica para onde ela pertence — dentro de cada estado.
Quando evitar
- Quando há apenas dois ou três estados simples e estáveis: um enum com uma condicional direta é mais legível do que criar toda a hierarquia de classes de Estado para uma máquina trivial.
- Quando o comportamento não muda por estado: se todos os estados executam os mesmos métodos com a mesma lógica, o padrão não adiciona valor — você está criando classes vazias.
Prós e contras
Prós
- Elimina grandes condicionais de estado espalhadas pelo código do contexto.
- Cada estado é uma classe isolada — fácil de testar, entender e modificar sem afetar outros estados.
- Novos estados podem ser adicionados sem alterar o contexto nem os estados existentes (Princípio Aberto/Fechado).
- Transições explícitas dentro de cada estado tornam o fluxo legível e auditável.
- Estados stateless podem ser compartilhados como singletons (otimização de memória).
Contras
- Explosão de classes para máquinas de estado com muitos estados — cada estado vira uma classe.
- A visão global das transições fica distribuída entre as classes de estado — mais difícil de enxergar o diagrama completo em um só lugar.
- Para máquinas simples, o overhead de classes é desnecessário — uma tabela de transições ou uma enum pode ser suficiente.
Armadilhas comuns
1. Onde colocar a lógica de transição
A decisão mais delicada: quem dispara a transição de estado — o Context ou o ConcreteState? O GoF recomenda que os próprios estados disparem as transições, pois eles conhecem as regras de quando e para onde transitar. Isso distribui o conhecimento de transição para cada estado, tornando cada um independente. O risco: estados que se referenciam mutuamente criam acoplamento circular. Para evitar isso, passe o Context para o estado (como mostrado nos exemplos) em vez de instanciar os estados diretamente dentro de outros estados quando possível.
2. Explosão de classes para máquinas simples
Regra prática: se a máquina tem menos de quatro estados e
as transições são simples, considere uma abordagem mais leve: uma enum de
estado + um método de transição com um switch centralizado no
Context. Reserve o padrão State completo para máquinas onde o comportamento
por estado é substancialmente diferente e as transições têm lógica relevante.
3. Estados com dados vs estados stateless
Se um estado precisa armazenar dados próprios (ex.: contador de tentativas em um estado de "reconectando"), ele não pode ser compartilhado como singleton — cada instância de Context precisa da sua própria instância do estado. Estados stateless são a exceção, não a regra. Tente manter os dados do contexto no Context e deixar os estados como pura lógica de comportamento e transição.
4. Confundir State com Strategy
Estruturalmente idênticos, mas a diferença de intenção importa na prática. Se você está modelando transições automáticas e os "objetos" se conhecem para transitar entre si — é State. Se o cliente escolhe e injeta o comportamento externamente e não há transições entre "estados" — é Strategy. A pergunta de diagnóstico: "quem decide a próxima variação — o próprio objeto ou o cliente externo?". Se o próprio objeto decide, é State.
Padrões relacionados
O State interage com outros padrões comportamentais e de criação:
O Strategy é o "irmão estrutural" do State: ambos encapsulam comportamento variável em objetos intercambiáveis, mas Strategy é controlado externamente pelo cliente (sem transições automáticas) enquanto State controla suas próprias transições. O Singleton é usado como otimização quando os estados são stateless — cada estado concreto sem dados próprios pode ser compartilhado como instância única entre todos os contextos. O Observer pode ser combinado com State para notificar componentes externos sobre transições de estado — o Context age como Subject e notifica Observers quando o estado muda.