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 como si el objeto hubiera cambiado de clase. En lugar de utilizar sentencias condicionales complejas para manejar diferentes estados, se encapsula cada estado como una clase separada.
El patrón consiste en un Contexto que mantiene una referencia al estado actual, una interfaz de Estado que define el comportamiento específico del estado y Estados Concretos que implementan el comportamiento para cada estado:
#include <iostream>
#include <memory>
class Document; // Declaración adelantada
// 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 maneja la acción publish() de manera diferente y es responsable de la transición al siguiente estado. El Document no necesita conocer la lógica de transición; simplemente delega al estado actual. Esto elimina las sentencias switch grandes y hace que añadir nuevos estados sea sencillo.
Utiliza State cuando el comportamiento de un objeto depende en gran medida de su estado y tienes muchas sentencias condicionales que cambian según el estado del objeto.
Desafío
FácilVamos a construir un Controlador de Semáforo utilizando el patrón State. Crearás un sistema donde un semáforo cicla a través de diferentes estados (Red, Yellow, Green), con cada estado determinando qué sucede cuando la luz cambia y qué mensaje se muestra. Este es un ejemplo clásico del patrón State: el comportamiento del semáforo depende enteramente de su estado actual.
Organizarás tu código en tres archivos:
TrafficLightState.h: Define la interfaz de estado y todas las clases de estado concretas.Crea una clase abstracta
TrafficLightStatecon:- Un método virtual puro
change(TrafficLight& light)que maneja la transición al siguiente estado - Un método virtual puro
getColor()que devuelve el color actual como un string - Un método virtual puro
getAction()que devuelve lo que los conductores deben hacer (por ejemplo,"Stop","Caution","Go") - Un destructor virtual
Implementa tres estados concretos:
RedState— el color es"Red", la acción es"Stop", cambia a GreenYellowState— el color es"Yellow", la acción es"Caution", cambia a RedGreenState— el color es"Green", la acción es"Go", cambia a Yellow
Necesitarás una declaración anticipada (forward declaration) para
TrafficLightya 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 por defecto. Implementa:setState(std::unique_ptr<TrafficLightState> newState)— cambia el estado actualchange()— delega al método change del estado actualdisplay()— imprime el estado del semáforo en 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 a realizar (entero).
Crea un
TrafficLighty muestra su estado inicial. Luego realiza el número especificado de cambios, mostrando 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 cicla a través de sus estados en un patrón predecible: Red → Green → Yellow → Red. Cada estado sabe qué estado sigue y maneja su propia transición. La clase TrafficLight no necesita ninguna lógica condicional para determinar qué sucede; simplemente delega a cualquier estado en el que se encuentre actualmente.
Hoja de referencia
El patrón State permite que un objeto cambie su comportamiento cuando su estado interno cambia. En lugar de utilizar sentencias condicionales complejas, cada estado se encapsula como una clase separada.
El patrón consta de tres componentes:
- Interfaz State: Define el comportamiento específico del estado
- Estados Concretos: Implementan el comportamiento para cada estado
- Contexto: Mantiene una referencia al estado actual
Estructura básica:
#include <memory>
class Context; // Declaración anticipada
// Interfaz State
class State {
public:
virtual void handleAction(Context& ctx) = 0;
virtual std::string getName() const = 0;
virtual ~State() = default;
};
// Contexto
class Context {
std::unique_ptr<State> state;
public:
void setState(std::unique_ptr<State> newState) {
state = std::move(newState);
}
void performAction() {
state->handleAction(*this);
}
std::string getStateName() const {
return state->getName();
}
};
// Estado Concreto
class ConcreteState : public State {
public:
void handleAction(Context& ctx) override {
// Manejar la acción y transicionar al siguiente estado
ctx.setState(std::make_unique<AnotherState>());
}
std::string getName() const override {
return "ConcreteState";
}
};Cada estado maneja las acciones de manera diferente y es responsable de la transición al siguiente estado. El Contexto delega el comportamiento al estado actual sin necesidad de conocer la lógica de transición.
Utilice State cuando el comportamiento de un objeto dependa en gran medida de su estado y desee evitar grandes sentencias condicionales.
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érico