Patrón State
Parte de la sección Programación Orientada a Objetos del Journey de C# de Coddy — lección 60 de 70.
El patrón State es un patrón de comportamiento que permite que un objeto cambie su comportamiento cuando su estado interno cambia. En lugar de utilizar sentencias condicionales complejas para gestionar diferentes estados, se encapsula cada estado en su propia clase y se delega el comportamiento al objeto de estado actual.
El patrón consta de tres componentes: una interfaz de estado que define el comportamiento específico del estado, clases de estado concretas que implementan dicho comportamiento y una clase de contexto que mantiene una referencia al estado actual:
public interface IDocumentState
{
void Publish(Document doc);
}
public class DraftState : IDocumentState
{
public void Publish(Document doc)
{
Console.WriteLine("Moving to moderation");
doc.SetState(new ModerationState());
}
}
public class ModerationState : IDocumentState
{
public void Publish(Document doc)
{
Console.WriteLine("Publishing document");
doc.SetState(new PublishedState());
}
}
public class PublishedState : IDocumentState
{
public void Publish(Document doc)
{
Console.WriteLine("Already published");
}
}El contexto mantiene el estado actual y le delega las acciones. Los objetos de estado pueden activar transiciones llamando a SetState() en el contexto:
public class Document
{
private IDocumentState _state = new DraftState();
public void SetState(IDocumentState state) => _state = state;
public void Publish() => _state.Publish(this);
}
var doc = new Document();
doc.Publish(); // Pasando a moderación
doc.Publish(); // Publicando documento
doc.Publish(); // Ya publicadoEl patrón State elimina las extensas sentencias if-else o switch que comprueban el estado actual. Cada clase de estado maneja solo su propio comportamiento, lo que hace que el código sea más fácil de mantener y extender con nuevos estados.
Desafío
FácilVamos a construir un sistema de semáforo utilizando el patrón State. Los semáforos pasan por distintos estados (rojo, amarillo, verde), y cada estado determina qué sucede cuando la luz cambia. En lugar de utilizar condicionales complejos, encapsularás cada estado de la luz en su propia clase.
Organizarás tu código en tres archivos:
TrafficLightState.cs: Define una interfazITrafficLightStateen el espacio de nombresTrafficcon un métodoChange(TrafficLight light). Luego crea tres clases de estado concretas:RedState- cuando se llama aChange, imprimeRed - Stop! Changing to Green...y transiciona el semáforo aGreenStateGreenState- imprimeGreen - Go! Changing to Yellow...y transiciona aYellowStateYellowState- imprimeYellow - Caution! Changing to Red...y transiciona aRedState
TrafficLight.cs: Crea una claseTrafficLighten el mismo espacio de nombres. Esta es tu clase de contexto que mantiene el estado actual. Debe comenzar enRedStatepor defecto y tener:- Un método
SetState(ITrafficLightState state)que los objetos de estado utilizan para activar las transiciones - Un método
Change()que delega al métodoChangedel estado actual
- Un método
Program.cs: Une todo creando unTrafficLighty ciclando a través de los cambios de estado basados en la entrada.
Recibirás una entrada:
- El número de veces que se debe cambiar el semáforo (un número entero)
Crea un TrafficLight (que comienza en rojo) y llama a Change() el número especificado de veces.
Por ejemplo, si la entrada es 3, la salida debería ser:
Red - Stop! Changing to Green...
Green - Go! Changing to Yellow...
Yellow - Caution! Changing to Red...Si la entrada es 5, el semáforo vuelve a ciclar:
Red - Stop! Changing to Green...
Green - Go! Changing to Yellow...
Yellow - Caution! Changing to Red...
Red - Stop! Changing to Green...
Green - Go! Changing to Yellow...Observa cómo la clase TrafficLight no contiene lógica sobre lo que significa cada color o qué viene después; todo ese conocimiento reside en las clases de estado individuales. ¡Cada estado es responsable de su propio comportamiento y de activar la transición al siguiente estado!
Hoja de referencia
El patrón State es un patrón de comportamiento que permite a un objeto cambiar su comportamiento cuando su estado interno cambia. En lugar de utilizar sentencias condicionales complejas, se encapsula cada estado en su propia clase y se delega el comportamiento al objeto de estado actual.
El patrón consta de tres componentes:
- Interfaz State: Define el comportamiento específico del estado.
- Clases de estado concretas: Implementan el comportamiento para cada estado.
- Clase Context: Mantiene una referencia al estado actual.
Ejemplo de implementación:
// State interface
public interface IDocumentState
{
void Publish(Document doc);
}
// Concrete state classes
public class DraftState : IDocumentState
{
public void Publish(Document doc)
{
Console.WriteLine("Moving to moderation");
doc.SetState(new ModerationState());
}
}
public class ModerationState : IDocumentState
{
public void Publish(Document doc)
{
Console.WriteLine("Publishing document");
doc.SetState(new PublishedState());
}
}
public class PublishedState : IDocumentState
{
public void Publish(Document doc)
{
Console.WriteLine("Already published");
}
}
// Context class
public class Document
{
private IDocumentState _state = new DraftState();
public void SetState(IDocumentState state) => _state = state;
public void Publish() => _state.Publish(this);
}
// Usage
var doc = new Document();
doc.Publish(); // Moving to moderation
doc.Publish(); // Publishing document
doc.Publish(); // Already publishedEl contexto mantiene el estado actual y le delega las acciones. Los objetos de estado pueden activar transiciones llamando a SetState() en el contexto. Esto elimina las extensas sentencias if-else o switch, haciendo que el código sea más fácil de mantener y extender con nuevos estados.
Pruébalo tú mismo
using System;
using Traffic;
class Program
{
public static void Main(String[] args)
{
// Lee el número de veces que se debe cambiar el semáforo
int n = Convert.ToInt32(Console.ReadLine());
// TODO: Crea una instancia de TrafficLight (comienza en RedState por defecto)
// TODO: Llama al método Change() n veces usando un bucle
}
}
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 externosNamespaces y directivasIntro a clases y objetosLa palabra clave 'this'Métodos y parámetrosCampos vs PropiedadesConstructoresInicializadores de objetosResumen - Calculadora simple4Herencia
Sintaxis básica de herencia (:)La palabra clave 'base'Palabras clave Virtual y OverrideClases selladas (Sealed)La clase base 'object'Resumen: Jerarquía de empleados7Características avanzadas
Sobrecarga de operadoresIndexadores (this[])Sobrescritura de ToString()Métodos de extensiónResumen - Lista personalizada10Patrones de diseño - Parte 1
Introducción a los patrones de diseñoSingleton Thread-SafePatrón FactoryPatrón Observer (Eventos)Patrón Strategy2Propiedades y miembros estáticos
Propiedades autoimplementadasPropiedades de solo lectura y escrituraCampos y métodos estáticosClases estáticasMiembros con cuerpo de expresión5Polimorfismo e Interfaces
Polimorfismo: Compilación vs. EjecuciónInterfaz vs. Clase AbstractaInterfaces MúltiplesInterfaces ExplícitasUpcasting y DowncastingRepaso: Calculadora de Figuras8Conceptos avanzados de POO
Composición sobre herenciaGenéricos (clases y métodos)Delegados y eventosAtributos y ReflexiónIDisposable y la sentencia usingFundamentos de Inyección de dependencias11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Arquitectura de clases
Datos de instancia vs. estáticosPalabras clave 'readonly' y 'const'Campos de respaldoResumen - Gestor de cuentas bancarias6Encapsulamiento
Modificadores de accesoPropiedades para el encapsulamientoImplementación de ocultamiento de datosPatrones de inmutabilidadRepaso - Registros de estudiantes