Patrón State
Parte de la sección Programación Orientada a Objetos del Journey de PHP de Coddy — lección 79 de 91.
El Patrón de Estado (State Pattern) es un patrón de diseño de comportamiento que permite que un objeto cambie su comportamiento cuando su estado interno cambia. En lugar de utilizar condicionales complejos para manejar diferentes estados, encapsulas cada estado en su propia clase y delegas el comportamiento al objeto de estado actual.
Piensa en una máquina expendedora. Se comporta de manera diferente dependiendo de si está esperando dinero, ha recibido el pago o está dispensando un producto.
Cada estado tiene sus propias reglas sobre qué acciones están permitidas. El Patrón State modela esto creando una clase 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";
}
}
La clase de contexto mantiene una referencia al estado actual y le delega las llamadas:
<?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();
Salida:
Pending
Order moved to processing
Processing
Order has been shipped
ShippedEl patrón State elimina las extensas sentencias switch y hace que añadir nuevos estados sea sencillo. Cada clase de estado es responsable de saber qué estado viene a continuación, manteniendo la lógica organizada y fácil de mantener.
Desafío
FácilVamos a construir un sistema de flujo de trabajo de documentos utilizando el Patrón State. Los documentos en un sistema de gestión de contenidos pasan por diferentes etapas — borrador, revisión y publicado — y cada etapa tiene sus propias reglas sobre qué acciones están permitidas y qué sucede cuando intentas avanzar el documento.
Organizarás tu código en cuatro archivos:
DocumentState.php— Define una interfazDocumentStateque establezca el contrato para todos los estados del documento. Debe tener dos métodos:publish(Document $document): stringpara intentar avanzar el documento, ygetStatus(): stringpara devolver el nombre del estado actual.States.php— Incluye la interfaz DocumentState y crea tres clases de estado que la implementen:DraftState— Cuando se llama apublish(), transiciona el documento aReviewStatey devuelve"Document sent for review". Su estado es"Draft".ReviewState— Cuando se llama apublish(), transiciona el documento aPublishedStatey devuelve"Document published". Su estado es"Under Review".PublishedState— Cuando se llama apublish(), devuelve"Document is already published"sin cambiar de estado. Su estado es"Published".
Document.php— Incluye el archivo States y crea una claseDocumentque actúe como el contexto. Tu documento debe:- Aceptar un título en su constructor e iniciar en el
DraftState - Tener un método
setState(DocumentState $state): voidpara cambiar el estado actual - Tener un método
publish(): stringque delegue al métodopublish()del estado actual - Tener un método
getStatus(): stringque devuelva el estado del estado actual
- Aceptar un título en su constructor e iniciar en el
main.php— Incluye el archivo Document. Recibirás una entrada: el número de veces que se debe llamar apublish()en el documento.Crea un
Documentcon el título"My Article". Primero, imprime el estado inicial. Luego, llama apublish()el número especificado de veces, imprimiendo el resultado de cada llamada en una nueva línea. Finalmente, imprime el estado final.Formato de salida:
Status: [initial status] [publish result 1] [publish result 2] ... Status: [final status]
Cada clase de estado sabe qué estado sigue en el flujo de trabajo, manteniendo la lógica de transición organizada y fácil de modificar. Añadir un nuevo estado como "Archived" simplemente significaría crear una nueva clase y actualizar las transiciones relevantes.
Hoja de referencia
El Patrón State es un patrón de diseño de comportamiento que permite a un objeto cambiar su comportamiento cuando su estado interno cambia, encapsulando cada estado en su propia clase.
Define una interfaz de estado que todas las clases de estado implementarán:
<?php
interface OrderState {
public function proceed(Order $order): string;
public function getStatus(): string;
}Crea clases de estado concretas que implementen la interfaz:
<?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";
}
}Crea una clase de contexto que mantenga una referencia al estado actual y le delegue las llamadas:
<?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();
}
}Ejemplo de uso:
$order = new Order();
echo $order->getStatus() . "\n"; // Pendiente
echo $order->proceed() . "\n"; // El pedido pasó a procesamiento
echo $order->getStatus() . "\n"; // Procesando
echo $order->proceed() . "\n"; // El pedido ha sido enviado
echo $order->getStatus(); // EnviadoEl Patrón State elimina las sentencias switch complejas y hace que añadir nuevos estados sea sencillo. Cada clase de estado es responsable de saber qué estado sigue.
Pruébalo tú mismo
<?php
require_once 'Document.php';
// Leer la entrada - número de veces que se debe llamar a publish()
$n = intval(trim(fgets(STDIN)));
// TODO: Crear un Document con el título "My Article"
// TODO: Imprimir el estado inicial en el formato: "Status: [status]"
// TODO: Llamar a publish() n veces, imprimiendo cada resultado en una nueva línea
// TODO: Imprimir el estado final en el formato: "Status: [status]"
?>Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosIntroducción a OOPClases vs ObjetosLa palabra clave $thisMétodosPropiedadesConstructor (__construct)Destructor (__destruct)Resumen - Calculadora simple4Herencia
Herencia básicaLa palabra clave parent::Sobrescritura de métodosLa palabra clave finalClases abstractasResumen - Jerarquía de empleados7Encapsulamiento
Público, Protegido, PrivadoModificadores de acceso a fondoGetters y SettersOcultamiento de informaciónPromoción de constructor (8.0)Resumen - Sistema de registros de estudiantes10Conceptos avanzados de POO
Composición vs. HerenciaInyección de dependenciasClases anónimasEnums (PHP 8.1)Fibers (PHP 8.1)Clonación de objetos a fondoGeneradores e Iteradores13Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Repository2Espacios de nombres y Autoloading
Introducción a los espacios de nombresLa palabra clave useEstándar de Autoloading PSR-4Autoloader de ComposerResumen - Proyecto organizado5Interfaces y Contratos
Introducción a las InterfacesImplementación de InterfacesImplementación de Múltiples InterfacesInterfaz vs Clase AbstractaType Hinting con InterfacesResumen - Calculadora de Formas8Métodos mágicos
Introducción a los métodos mágicos__toString y __debugInfo__get, __set, __isset, __unset__call y __callStatic__clone y clonación de objetos__serialize y __unserializeResumen - Colección personalizada3Propiedades de clase
Propiedades de instancia vs. estáticasConstantes en clasesMétodos y propiedades estáticosPropiedades privadas y protegidasPropiedades de solo lectura (PHP 8.1)Resumen - Gestor de cuenta bancaria6Polimorfismo
Sobrescritura de métodos revisadaPolimorfismo mediante interfacesType Hinting y tipos de uniónEnlace estático tardíoResumen - Procesador de pagos9Traits
Introducción a los TraitsUso de múltiples TraitsResolución de conflictos de TraitsMétodos abstractos en TraitsTraits frente a herencia12Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy