Descobrir o State Pattern foi um ponto de virada. Esse padrão não é o mais falado, mas resolve um problema real: código inchado por condicionais monstruosas.
Sabe aquelas máquinas de estado cheias de if
e switch
? O código cresce, novos estados surgem e, de repente, qualquer alteração vira uma dor de cabeça. Já viu algo assim?
class User {
constructor(name) {
this.name = name;
this.status = "pending"; // Estado inicial
}
changeStatus(newStatus) {
switch (newStatus) {
case "active":
if (this.status === "active") {
console.log("Já está ativo...");
} else {
this.status = "active";
console.log("Usuário ativado com sucesso.");
}
break;
case "blocked":
if (this.status === "blocked") {
console.log("Usuário já está bloqueado.");
} else {
this.status = "blocked";
console.log("Usuário bloqueado com sucesso.");
}
break;
case "canceled":
if (this.status === "canceled") {
console.log("Usuário já está cancelado.");
} else {
this.status = "canceled";
console.log("Usuário cancelado com sucesso.");
}
break;
default:
console.log("Status inválido.");
}
}
getStatus() {
console.log(`Status atual: ${this.status}`);
}
}
No começo, parece tranquilo. Mas conforme o sistema evolui, o inferno começa: um mar de condicionais espalhadas pelo código. Manutenção? Um pesadelo.
Solução
O State Pattern resolve isso. Em vez de espalhar lógica de estado, cada estado vira uma classe separada. O objeto principal apenas mantém uma referência ao estado atual e delega as decisões para ele.
Menos bagunça, mais organização. E quando precisar adicionar um novo estado? Só criar uma nova classe, sem tocar no código existente. Quer código flexível e fácil de manter? State Pattern é o caminho.
Exemplo
Aqui está um exemplo de implementação do State Pattern em TypeScript para modelar o status de uma conta (Account), que pode estar em um dos seguintes estados:
Active
WaitingActivation
Blocked
Cancelled
Cada estado implementará um comportamento diferente para a conta. Vamos definir as classes de estado e a classe de contexto (Account
), que gerencia o estado atual.
📌 Passo 1: Definir a Interface do Estado
Cada estado da conta precisa implementar essa interface para garantir que todos os estados tenham os mesmos métodos.
interface AccountState {
activate(): void;
block(): void;
cancel(): void;
getStatus(): string;
}
📌 Passo 2: Criar as Implementações dos Estados
Cada estado terá uma implementação específica, alterando o comportamento da conta de acordo com a regra de negócio.
class ActiveState implements AccountState {
constructor(private account: Account) {}
activate(): void {
console.log("A conta já está ativa.");
}
block(): void {
console.log("Bloqueando a conta...");
this.account.setState(new BlockedState(this.account));
}
cancel(): void {
console.log("Cancelando a conta...");
this.account.setState(new CancelledState(this.account));
}
getStatus(): string {
return "Active";
}
}
class WaitingActivationState implements AccountState {
constructor(private account: Account) {}
activate(): void {
console.log("Ativando a conta...");
this.account.setState(new ActiveState(this.account));
}
block(): void {
console.log("A conta ainda não está ativada e não pode ser bloqueada.");
}
cancel(): void {
console.log("Cancelando a conta antes da ativação...");
this.account.setState(new CancelledState(this.account));
}
getStatus(): string {
return "WaitingActivation";
}
}
class BlockedState implements AccountState {
constructor(private account: Account) {}
activate(): void {
console.log("Não é possível ativar uma conta bloqueada. Contate o suporte.");
}
block(): void {
console.log("A conta já está bloqueada.");
}
cancel(): void {
console.log("Cancelando a conta bloqueada...");
this.account.setState(new CancelledState(this.account));
}
getStatus(): string {
return "Blocked";
}
}
class CancelledState implements AccountState {
constructor(private account: Account) {}
activate(): void {
console.log("Uma conta cancelada não pode ser reativada.");
}
block(): void {
console.log("Uma conta cancelada não pode ser bloqueada.");
}
cancel(): void {
console.log("A conta já está cancelada.");
}
getStatus(): string {
return "Cancelled";
}
}
📌 Passo 3: Criar a Classe Account
Essa classe é sua entidade e gerencia o estado atual da conta e delega chamadas para o estado correto.
class Account {
private state: AccountState;
constructor() {
// Estado inicial: aguardando ativação
this.state = new WaitingActivationState(this);
}
setState(state: AccountState): void {
this.state = state;
}
activate(): void {
this.state.activate();
}
block(): void {
this.state.block();
}
cancel(): void {
this.state.cancel();
}
getStatus(): string {
return this.state.getStatus();
}
}
📌 Passo 4: Testar a Implementação
Agora, vamos criar uma conta e testar as transições de estado, a execução seria algo assim:
const myAccount = new Account();
console.log("Status inicial:", myAccount.getStatus());
myAccount.activate(); // Deve ativar a conta
console.log("Status atual:", myAccount.getStatus());
myAccount.block(); // Deve bloquear a conta
console.log("Status atual:", myAccount.getStatus());
myAccount.cancel(); // Deve cancelar a conta
console.log("Status atual:", myAccount.getStatus());
myAccount.activate(); // Tentativa de ativar uma conta cancelada (deve falhar)
console.log("Status final:", myAccount.getStatus());
📌 Explicação
- Encapsulamento dos estados: Cada estado implementa um comportamento específico e sabe para qual estado pode transitar.
-
Mudança dinâmica de comportamento: A classe
Account
delega a execução das ações ao estado atual. -
Evita
if-else
desnecessários: O código fica mais organizado e escalável.
Isso garante uma implementação flexível e permite adicionar novos estados sem modificar a lógica central da Account
. 🚀
Conclusão
No State Pattern que a gente montou, vários princípios entraram em jogo sem a gente nem precisar pensar muito. Olha só:
Encapsulamento? Sim Cada estado tem sua própria lógica, sem espalhar código bagunçado por aí. O Account
nem precisa saber como cada estado funciona, só delega e segue o jogo.
Responsabilidade Única? Sim Cada classe faz uma coisa só. O estado lida com as regras do status da conta, e o Account
só troca de estado quando precisa. Sem mistura de responsabilidades.
Aberto/Fechado? Sim. Se amanhã precisar de um novo estado tipo SuspendedState
, só criar e encaixar. Não precisa mexer no código velho e correr risco de quebrar tudo.
Substituição de Liskov? Sim. O Account
nem sabe qual estado tá rodando. Ele só chama os métodos da interface e confia que o estado resolve. Pode trocar um pelo outro sem dor de cabeça.
Inversão de Dependência? Sim. O Account
depende da abstração (AccountState
), não de implementações específicas. Zero acoplamento, total flexibilidade.
No fim, isso aqui é código escalável, fácil de manter e sem aquele monte de if
espalhado. Quer mudar um comportamento? Troca o estado e pronto. Isso é POO do jeito certo. 🚀
Links
https://refactoring.guru/pt-br/design-patterns/state
https://elemarjr.com/clube-de-estudos/artigos/voce-precisa-conhecer-o-state-pattern/
Top comments (0)