patron-state

Patrón State: comportamiento según el estado

  • 5 min

El State es un patrón que delega el comportamiento de un contexto en un objeto que representa su estado actual. Cuando el estado cambia, el mismo contexto responde de otra forma sin llenar todos sus métodos de condicionales.

Imagina un Documento en un gestor de contenidos (CMS).

  1. Empieza como Borrador.
  2. Pasa a Moderación.
  3. Finalmente se convierte en Publicado.

El comportamiento del método Publicar() cambia radicalmente según en qué fase estemos.

  • Si es Borrador, Publicar mueve el documento a Moderación.
  • Si es Moderación, Publicar (si eres admin) lo hace visible al mundo.
  • Si ya es Publicado, Publicar no debería hacer nada o lanzar un error.

El Problema: La Máquina de Estados Monolítica

La forma intuitiva (y mala) de resolver esto es usar una variable simple para guardar el estado y llenar los métodos de condicionales.

// ❌ El anti-patrón del Switch Gigante
class Documento
{
    public string Estado = "Borrador";

    public void Publicar()
    {
        switch (Estado)
        {
            case "Borrador":
                Estado = "Moderacion";
                break;
            case "Moderacion":
                Estado = "Publicado"; // Solo si es admin...
                break;
            case "Publicado":
                Console.WriteLine("¡Ya está publicado!");
                break;
        }
    }
}
Copied!

Esto se vuelve inmanejable rápidamente. Si añadimos un nuevo estado (Archivado), tenemos que revisar todos los métodos de la clase Documento y añadir un nuevo case.

El patrón State sugiere que cada estado sea una clase propia. El documento delegará la ejecución del comportamiento al objeto que represente su estado actual.

La Solución: “Soy lo que es mi estado”

La idea principal es que el objeto original (Contexto) mantiene una referencia a un objeto de estado. Cuando le piden hacer algo, el Contexto dice: “No sé, pregúntale a mi Estado actual”.

Esto permite que el objeto parezca cambiar de clase en tiempo de ejecución.

Implementación en C#

Vamos a implementar el flujo de nuestro Documento.

La Interfaz de Estado (State)

Definimos las acciones posibles en nuestro sistema. Fíjate que los métodos reciben el Documento (contexto) para poder cambiar su estado.

// State (Abstracto o Interfaz)
public abstract class EstadoDocumento
{
    // Métodos abstractos para las acciones posibles
    public abstract void Publicar(Documento contexto);
    public abstract void Rechazar(Documento contexto);
}
Copied!

El Contexto (Documento)

Esta es la clase que usa el cliente.

public class Documento
{
    // El estado actual
    private EstadoDocumento _estadoActual;

    public Documento()
    {
        // Estado inicial
        _estadoActual = new EstadoBorrador();
    }

    // Método para cambiar de estado (usado por los estados concretos)
    public void TransicionA(EstadoDocumento nuevoEstado)
    {
        Console.WriteLine($"Contexto: Transición de {_estadoActual.GetType().Name} a {nuevoEstado.GetType().Name}");
        _estadoActual = nuevoEstado;
    }

    // Acciones del documento: Delegan en el estado
    public void Publicar() => _estadoActual.Publicar(this);
    public void Rechazar() => _estadoActual.Rechazar(this);
}
Copied!

Los Estados Concretos

Aquí está la lógica de negocio y las reglas de transición. Cada clase es responsable de su propio comportamiento.

// Estado 1: Borrador
public class EstadoBorrador : EstadoDocumento
{
    public override void Publicar(Documento contexto)
    {
        Console.WriteLine("Borrador -> Enviando a moderación...");
        context.TransicionA(new EstadoModeracion());
    }

    public override void Rechazar(Documento contexto)
    {
        Console.WriteLine("Borrador -> Borrando borrador. ¡Adiós!");
        // Lógica de borrado...
    }
}

// Estado 2: Moderación
public class EstadoModeracion : EstadoDocumento
{
    public override void Publicar(Documento contexto)
    {
        Console.WriteLine("Moderación -> ¡Aprobado! Publicando en la web.");
        context.TransicionA(new EstadoPublicado());
    }

    public override void Rechazar(Documento contexto)
    {
        Console.WriteLine("Moderación -> Rechazado. Vuelve a ser borrador.");
        context.TransicionA(new EstadoBorrador());
    }
}

// Estado 3: Publicado
public class EstadoPublicado : EstadoDocumento
{
    public override void Publicar(Documento contexto)
    {
        Console.WriteLine("Publicado -> No hacer nada, ya está publicado.");
    }

    public override void Rechazar(Documento contexto)
    {
        Console.WriteLine("Publicado -> Retirando publicación. Vuelve a Borrador.");
        context.TransicionA(new EstadoBorrador());
    }
}
Copied!

Ejecución

class Program
{
    static void Main(string[] args)
    {
        var doc = new Documento();

        // Estamos en Borrador
        doc.Publicar(); // Pasa a Moderación

        // Estamos en Moderación
        doc.Publicar(); // Pasa a Publicado

        // Estamos en Publicado
        doc.Publicar(); // No hace nada

        // Retiramos el documento
        doc.Rechazar(); // Vuelve a Borrador
    }
}
Copied!

Salida:

Borrador -> Enviando a moderación...
Contexto: Transición de EstadoBorrador a EstadoModeracion
Moderación -> ¡Aprobado! Publicando en la web.
Contexto: Transición de EstadoModeracion a EstadoPublicado
Publicado -> No hacer nada, ya está publicado.
Publicado -> Retirando publicación. Vuelve a Borrador.
Contexto: Transición de EstadoPublicado a EstadoBorrador
Copied!

State vs Strategy

Esta es la pregunta del millón, porque los diagramas UML son casi idénticos.

CaracterísticaStrategyState
Origen del cambioEl Cliente decide cambiar la estrategia (“Ahora quiero ruta a pie”).El propio Estado o el Contexto deciden el cambio (“He terminado el borrador, paso a moderación”).
ConocimientoLas estrategias suelen ser independientes entre sí.Los estados suelen conocerse entre sí (un estado crea al siguiente).
PropósitoIntercambiar algoritmos.Modelar el ciclo de vida o fases de un objeto.

¿Cuándo usar State?

  1. Máquinas de Estados: Cuando tienes un objeto que se comporta como una máquina de estados finitos (FSM).
  2. Condicionales complejos: Cuando tienes métodos gigantes llenos de switch que dependen del estado del objeto.
  3. Cambios en tiempo de ejecución: Cuando el comportamiento de un objeto debe cambiar drásticamente según sus campos internos.

El patrón State limpia nuestro código al distribuir la lógica de cada fase en su propia clase. Facilita enormemente añadir nuevos estados sin romper los existentes (Principio Open/Closed).