Padrão State
Parte da seção Programação Orientada a Objetos do Journey de PHP da Coddy — lição 79 de 91.
O State Pattern é um padrão de projeto comportamental que permite que um objeto altere seu comportamento quando seu estado interno muda. Em vez de usar condicionais complexas para lidar com diferentes estados, você encapsula cada estado em sua própria classe e delega o comportamento ao objeto de estado atual.
Pense em uma máquina de venda automática. Ela se comporta de maneira diferente dependendo se está aguardando o dinheiro, se recebeu o pagamento ou se está dispensando um produto.
Cada estado tem suas próprias regras para quais ações são permitidas. O State Pattern modela isso criando uma classe para cada estado.
<?php
interface OrderState {
public function proceed(Order $order): string;
public function getStatus(): string;
}
class PendingState implements OrderState {
public function proceed(Order $order): string {
$order->setState(new ProcessingState());
return "Order moved to processing";
}
public function getStatus(): string {
return "Pending";
}
}
class ProcessingState implements OrderState {
public function proceed(Order $order): string {
$order->setState(new ShippedState());
return "Order has been shipped";
}
public function getStatus(): string {
return "Processing";
}
}
class ShippedState implements OrderState {
public function proceed(Order $order): string {
return "Order already shipped";
}
public function getStatus(): string {
return "Shipped";
}
}
A classe de contexto mantém uma referência ao estado atual e delega as chamadas a ele:
<?php
class Order {
private OrderState $state;
public function __construct() {
$this->state = new PendingState();
}
public function setState(OrderState $state): void {
$this->state = $state;
}
public function proceed(): string {
return $this->state->proceed($this);
}
public function getStatus(): string {
return $this->state->getStatus();
}
}
$order = new Order();
echo $order->getStatus() . "\n";
echo $order->proceed() . "\n";
echo $order->getStatus() . "\n";
echo $order->proceed() . "\n";
echo $order->getStatus();
Saída:
Pending
Order moved to processing
Processing
Order has been shipped
ShippedO Padrão State elimina instruções switch extensas e torna a adição de novos estados algo direto. Cada classe de estado é responsável por saber qual estado vem a seguir, mantendo a lógica organizada e fácil de manter.
Desafio
FácilVamos construir um sistema de fluxo de trabalho de documentos usando o State Pattern. Documentos em um sistema de gerenciamento de conteúdo passam por diferentes estágios — rascunho, revisão e publicado — com cada estágio tendo suas próprias regras sobre quais ações são permitidas e o que acontece quando você tenta avançar o documento.
Você organizará seu código em quatro arquivos:
DocumentState.php— Defina uma interfaceDocumentStateque estabelece o contrato para todos os estados do documento. Ela deve ter dois métodos:publish(Document $document): stringpara tentar avançar o documento, egetStatus(): stringpara retornar o nome do estado atual.States.php— Inclua a interface DocumentState e crie três classes de estado que a implementam:DraftState— Quandopublish()é chamado, ele transiciona o documento paraReviewStatee retorna"Document sent for review". Seu status é"Draft".ReviewState— Quandopublish()é chamado, ele transiciona o documento paraPublishedStatee retorna"Document published". Seu status é"Under Review".PublishedState— Quandopublish()é chamado, ele retorna"Document is already published"sem alterar o estado. Seu status é"Published".
Document.php— Inclua o arquivo States e crie uma classeDocumentque atua como o contexto. Seu documento deve:- Aceitar um título em seu construtor e começar no
DraftState - Ter um método
setState(DocumentState $state): voidpara alterar o estado atual - Ter um método
publish(): stringque delega para o métodopublish()do estado atual - Ter um método
getStatus(): stringque retorna o status do estado atual
- Aceitar um título em seu construtor e começar no
main.php— Inclua o arquivo Document. Você receberá uma entrada: o número de vezes que deve chamarpublish()no documento.Crie um
Documentcom o título"My Article". Primeiro, imprima o status inicial. Em seguida, chamepublish()o número especificado de vezes, imprimindo o resultado de cada chamada em uma nova linha. Finalmente, imprima o status final.Formato de saída:
Status: [initial status] [publish result 1] [publish result 2] ... Status: [final status]
Cada classe de estado sabe qual estado vem a seguir no fluxo de trabalho, mantendo a lógica de transição organizada e fácil de modificar. Adicionar um novo estado como "Archived" significaria simplesmente criar uma nova classe e atualizar as transições relevantes.
Folha de consulta
O State Pattern (Padrão de Estado) é um padrão de design comportamental que permite que um objeto altere seu comportamento quando seu estado interno muda, encapsulando cada estado em sua própria classe.
Defina uma interface de estado que todas as classes de estado implementarão:
<?php
interface OrderState {
public function proceed(Order $order): string;
public function getStatus(): string;
}Crie classes de estado concretas que implementam a interface:
<?php
class PendingState implements OrderState {
public function proceed(Order $order): string {
$order->setState(new ProcessingState());
return "Order moved to processing";
}
public function getStatus(): string {
return "Pending";
}
}
class ProcessingState implements OrderState {
public function proceed(Order $order): string {
$order->setState(new ShippedState());
return "Order has been shipped";
}
public function getStatus(): string {
return "Processing";
}
}
class ShippedState implements OrderState {
public function proceed(Order $order): string {
return "Order already shipped";
}
public function getStatus(): string {
return "Shipped";
}
}Crie uma classe de contexto que mantém uma referência ao estado atual e delega as chamadas para ele:
<?php
class Order {
private OrderState $state;
public function __construct() {
$this->state = new PendingState();
}
public function setState(OrderState $state): void {
$this->state = $state;
}
public function proceed(): string {
return $this->state->proceed($this);
}
public function getStatus(): string {
return $this->state->getStatus();
}
}Exemplo de uso:
$order = new Order();
echo $order->getStatus() . "\n"; // Pending
echo $order->proceed() . "\n"; // Order moved to processing
echo $order->getStatus() . "\n"; // Processing
echo $order->proceed() . "\n"; // Order has been shipped
echo $order->getStatus(); // ShippedO State Pattern elimina declarações switch complexas e torna a adição de novos estados algo simples. Cada classe de estado é responsável por saber qual estado vem a seguir.
Experimente você mesmo
<?php
require_once 'Document.php';
// Ler entrada - número de vezes para chamar publish()
$n = intval(trim(fgets(STDIN)));
// TODO: Criar um Document com o título "My Article"
// TODO: Imprimir o status inicial no formato: "Status: [status]"
// TODO: Chamar publish() n vezes, imprimindo cada resultado em uma nova linha
// TODO: Imprimir o status final no formato: "Status: [status]"
?>Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de POO
Arquivos ExternosIntrodução à POOClasses vs ObjetosA Palavra-chave $thisMétodosPropriedadesConstrutor (__construct)Destrutor (__destruct)Recapitulação - Calculadora Simples4Herança
Herança BásicaA Palavra-chave parent::Sobrescrita de MétodoA Palavra-chave finalClasses AbstratasRecapitulação - Hierarquia de Funcionários7Encapsulamento
Público, Protegido, PrivadoModificadores de Acesso em DetalhesGetters e SettersOcultação de InformaçãoPromoção de Construtor (8.0)Recapitulação - Sistema de Registro de Alunos10Conceitos Avançados de POO
Composição vs HerançaInjeção de DependênciaClasses AnônimasEnums (PHP 8.1)Fibers (PHP 8.1)Mergulho Profundo em Clonagem de ObjetosGeradores e Iteradores13Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositePadrão Repository2Namespaces e Autoloading
Introdução a NamespacesA Palavra-chave usePadrão de Autoloading PSR-4Autoloader do ComposerRecapitulação - Projeto Organizado5Interfaces e Contratos
Introdução a InterfacesImplementando InterfacesImplementação de Múltiplas InterfacesInterface vs Classe AbstrataType Hinting com InterfacesRecapitulação - Calculadora de Formas8Métodos Mágicos
Introdução aos Métodos Mágicos__toString e __debugInfo__get, __set, __isset, __unset__call e __callStatic__clone e Clonagem de Objetos__serialize e __unserializeRecapitulação - Coleção Personalizada11Sistema de Tipos e Tratamento de Erros
Declarações de TiposTipos AnuláveisTipos de União e InterseçãoClasses de ExceçãoHierarquia de Exceções PersonalizadasTry, Catch, FinallyRecapitulação - Validador de Formulário14Projeto: Gerenciamento de Biblioteca
Visão Geral do ProjetoClasses Livro e Usuário3Propriedades de Classe
Propriedades de Instância vs EstáticasConstantes em ClassesMétodos e Propriedades EstáticosPropriedades Privadas e ProtegidasPropriedades Readonly (PHP 8.1)Recapitulação - Gerenciador de Conta Bancária6Polimorfismo
Sobrescrita de Métodos RevisitadaPolimorfismo via InterfacesType Hinting e Union TypesLate Static BindingRecapitulação - Processador de Pagamentos9Traits
Introdução a TraitsUsando Múltiplos TraitsResolução de Conflitos de TraitsMétodos Abstratos em TraitsTraits vs Herança12Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão ObserverPadrão Strategy