Menu
Coddy logo textTech

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
Shipped

El 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.

challenge icon

Desafío

Fácil

Vamos 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 interfaz DocumentState que establezca el contrato para todos los estados del documento. Debe tener dos métodos: publish(Document $document): string para intentar avanzar el documento, y getStatus(): string para 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 a publish(), transiciona el documento a ReviewState y devuelve "Document sent for review". Su estado es "Draft".
    • ReviewState — Cuando se llama a publish(), transiciona el documento a PublishedState y devuelve "Document published". Su estado es "Under Review".
    • PublishedState — Cuando se llama a publish(), devuelve "Document is already published" sin cambiar de estado. Su estado es "Published".
  • Document.php — Incluye el archivo States y crea una clase Document que 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): void para cambiar el estado actual
    • Tener un método publish(): string que delegue al método publish() del estado actual
    • Tener un método getStatus(): string que devuelva el estado del estado actual
  • main.php — Incluye el archivo Document. Recibirás una entrada: el número de veces que se debe llamar a publish() en el documento.

    Crea un Document con el título "My Article". Primero, imprime el estado inicial. Luego, llama a publish() 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();          // Enviado

El 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]"

?>
quiz iconPonte a prueba

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