Patrón State
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy. Lección 100 de 104.
El patrón State permite que un objeto cambie su comportamiento cuando cambia su estado interno, haciendo que parezca que el objeto cambió de clase. En lugar de usar instrucciones condicionales complejas para gestionar diferentes estados, encapsulas cada estado como una clase independiente.
El patrón consta de un Contexto que mantiene una referencia al estado actual, una interfaz State que define el comportamiento específico de cada estado y Estados concretos que implementan el comportamiento de cada estado:
#include <iostream>
#include <memory>
class Document; // Declaración anticipada
// Interfaz de estado
class DocumentState {
public:
virtual void publish(Document& doc) = 0;
virtual std::string getName() const = 0;
virtual ~DocumentState() = default;
};
// Contexto
class Document {
std::unique_ptr<DocumentState> state;
public:
Document();
void setState(std::unique_ptr<DocumentState> newState) {
state = std::move(newState);
}
void publish() { state->publish(*this); }
std::string getStateName() const { return state->getName(); }
};
// Estados concretos
class Draft : public DocumentState {
public:
void publish(Document& doc) override;
std::string getName() const override { return "Draft"; }
};
class Review : public DocumentState {
public:
void publish(Document& doc) override;
std::string getName() const override { return "Review"; }
};
class Published : public DocumentState {
public:
void publish(Document& doc) override {
std::cout << "Already published\n";
}
std::string getName() const override { return "Published"; }
};
void Draft::publish(Document& doc) {
std::cout << "Moving to review\n";
doc.setState(std::make_unique<Review>());
}
void Review::publish(Document& doc) {
std::cout << "Publishing document\n";
doc.setState(std::make_unique<Published>());
}
Document::Document() : state(std::make_unique<Draft>()) {}Cada estado gestiona la acción publish() de forma diferente y es responsable de pasar al estado siguiente. El Document no necesita conocer la lógica de transición: simplemente delega en el estado actual. Esto elimina las grandes instrucciones switch y facilita añadir nuevos estados.
Usa State cuando el comportamiento de un objeto dependa en gran medida de su estado y tengas muchas instrucciones condicionales que cambien según el estado del objeto.
Desafío
FácilConstruyamos un Controlador de semáforo utilizando el patrón State. Crearás un sistema en el que un semáforo pasa por diferentes estados (Red, Yellow, Green), y cada estado determina qué ocurre cuando cambia el semáforo y qué mensaje se muestra. Este es un ejemplo clásico del patrón State: el comportamiento del semáforo depende por completo de su estado actual.
Organizarás tu código en tres archivos:
TrafficLightState.h: Define la interfaz del estado y todas las clases de estado concretas.Crea una clase abstracta
TrafficLightStatecon:- Un método virtual puro
change(TrafficLight& light)que gestiona la transición al estado siguiente - Un método virtual puro
getColor()que devuelve el color actual como una cadena - Un método virtual puro
getAction()que devuelve lo que deben hacer los conductores (por ejemplo, "Stop", "Caution", "Go") - Un destructor virtual
Implementa tres estados concretos:
RedState: el color es"Red", la acción es"Stop"y cambia a GreenYellowState: el color es"Yellow", la acción es"Caution"y cambia a RedGreenState: el color es"Green", la acción es"Go"y cambia a Yellow
Necesitarás una declaración anticipada de
TrafficLight, ya que los estados hacen referencia a ella.- Un método virtual puro
TrafficLight.h: Crea la clase de contexto que mantiene el estado actual.Tu clase
TrafficLightdebe contener unstd::unique_ptr<TrafficLightState>e iniciar en el estado Red de forma predeterminada. Implementa:setState(std::unique_ptr<TrafficLightState> newState): cambia el estado actualchange(): delega en el método change del estado actualdisplay(): imprime el estado del semáforo con el formato:[Color]: [Action]
main.cpp: Demuestra el ciclo del semáforo a través de los estados.Lee una entrada: el número de cambios de estado que se deben realizar (un entero).
Crea un
TrafficLighty muestra su estado inicial. Después, realiza el número especificado de cambios y muestra el estado después de cada cambio.
Por ejemplo, con la entrada 3:
Red: Stop
Green: Go
Yellow: Caution
Red: StopCon la entrada 6:
Red: Stop
Green: Go
Yellow: Caution
Red: Stop
Green: Go
Yellow: Caution
Red: StopObserva cómo el semáforo recorre sus estados siguiendo un patrón predecible: Red → Green → Yellow → Red. Cada estado sabe qué estado viene después y gestiona su propia transición. La clase TrafficLight no necesita ninguna lógica condicional para determinar qué ocurre. Simplemente delega en el estado en el que se encuentra actualmente.
Pruébalo tú mismo
#include <iostream>
#include "TrafficLight.h"
using namespace std;
int main() {
// Lee el número de cambios de estado
int numChanges;
cin >> numChanges;
// TODO: Crea un objeto TrafficLight
// TODO: Muestra el estado inicial
// TODO: Realiza el número especificado de cambios
// Después de cada cambio, muestra el estado actual
return 0;
}
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 externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genéricoPractica por tu cuenta: Compilador de C++ online