Patrón Observer
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 94 de 104.
El patrón Observer define una dependencia de uno a muchos entre objetos: cuando un objeto (el subject) cambia de estado, todos sus dependientes (los observers) son notificados automáticamente. Esto es ideal para sistemas de eventos, actualizaciones de UI o cualquier escenario donde múltiples objetos necesiten reaccionar a los cambios.
El patrón involucra dos roles clave: un Sujeto que mantiene una lista de observadores y les notifica, y Observadores que implementan una interfaz de actualización:
#include <iostream>
#include <vector>
#include <algorithm>
class Observer {
public:
virtual void update(int value) = 0;
virtual ~Observer() = default;
};
class Subject {
std::vector<Observer*> observers;
int state = 0;
public:
void attach(Observer* obs) {
observers.push_back(obs);
}
void detach(Observer* obs) {
observers.erase(
std::remove(observers.begin(), observers.end(), obs),
observers.end());
}
void setState(int value) {
state = value;
notify();
}
void notify() {
for (Observer* obs : observers) {
obs->update(state);
}
}
};
class Display : public Observer {
std::string name;
public:
Display(const std::string& n) : name(n) {}
void update(int value) override {
std::cout << name << " received: " << value << "\n";
}
};
int main() {
Subject sensor;
Display screen1("Screen1"), screen2("Screen2");
sensor.attach(&screen1);
sensor.attach(&screen2);
sensor.setState(42); // Ambas pantallas notificadas
}Cuando se llama a setState(), el sujeto itera a través de todos los observadores registrados y llama a su método update(). Los observadores pueden adjuntarse o desvincularse en cualquier momento, lo que hace que el sistema sea flexible y esté débilmente acoplado.
Utiliza Observer cuando los cambios en un objeto requieran actualizar otros, y no quieras que estos objetos estén estrechamente acoplados entre sí.
Desafío
FácilVamos a construir un sistema de monitoreo de una Estación Meteorológica utilizando el patrón Observer. Crearás una estación meteorológica que rastrea datos de temperatura y notifica automáticamente a múltiples unidades de visualización cada vez que la temperatura cambia, una aplicación clásica del mundo real de este patrón.
Organizarás tu código en tres archivos:
Observer.h: Define la interfaz del observador y un observador de visualización concreto.Crea una clase abstracta
Observercon un método virtual puroupdate(double temperature)que los observadores implementan para recibir notificaciones, junto con un destructor virtual.Luego, crea una clase
TemperatureDisplayque herede deObserver. Cada pantalla tiene un nombre (string) establecido a través de su constructor. Cuando se llama aupdate(), debe imprimir:[DisplayName]: Temperature is [temperature] degreesWeatherStation.h: Crea el sujeto que mantiene la temperatura y notifica a los observadores.Tu clase
WeatherStationdebe:- Almacenar una lista de punteros a observadores y la temperatura actual (inicializar a
0.0) - Tener un método
attach(Observer* obs)para registrar observadores - Tener un método
detach(Observer* obs)para eliminar observadores - Tener un método
setTemperature(double temp)que actualice la temperatura y notifique a todos los observadores adjuntos - Tener un método privado
notify()que llame aupdate()en cada observador con la temperatura actual
- Almacenar una lista de punteros a observadores y la temperatura actual (inicializar a
main.cpp: Demuestra el patrón Observer en acción.Lee tres entradas:
- Nombre para la primera pantalla (string)
- Nombre para la segunda pantalla (string)
- Un valor de temperatura (double)
Crea una
WeatherStationy dos objetosTemperatureDisplaycon los nombres proporcionados. Adjunta ambas pantallas a la estación, luego establece la temperatura al valor de entrada. Ambas pantallas deberían recibir automáticamente la actualización e imprimir sus mensajes.Después de eso, desvincula la primera pantalla y establece una nueva temperatura que sea 5 grados superior a la de entrada. Solo la segunda pantalla debería recibir esta actualización.
Por ejemplo, con las entradas Kitchen, Bedroom, y 22.5:
Kitchen: Temperature is 22.5 degrees
Bedroom: Temperature is 22.5 degrees
Bedroom: Temperature is 27.5 degreesCon las entradas Office, Lobby, y 18.0:
Office: Temperature is 18 degrees
Lobby: Temperature is 18 degrees
Lobby: Temperature is 23 degreesObserva cómo el patrón Observer crea un sistema débilmente acoplado: la estación meteorológica no necesita saber nada sobre las pantallas específicas, simplemente notifica a quien esté escuchando. Cuando desvinculas un observador, este deja de recibir actualizaciones automáticamente.
Hoja de referencia
El patrón Observer define una dependencia de uno a muchos donde un sujeto notifica a todos sus observadores automáticamente cuando su estado cambia.
El patrón tiene dos roles clave:
- Subject: Mantiene una lista de observadores y les notifica los cambios de estado
- Observer: Implementa una interfaz de actualización para recibir notificaciones
Implementación básica:
#include <iostream>
#include <vector>
#include <algorithm>
class Observer {
public:
virtual void update(int value) = 0;
virtual ~Observer() = default;
};
class Subject {
std::vector<Observer*> observers;
int state = 0;
public:
void attach(Observer* obs) {
observers.push_back(obs);
}
void detach(Observer* obs) {
observers.erase(
std::remove(observers.begin(), observers.end(), obs),
observers.end());
}
void setState(int value) {
state = value;
notify();
}
void notify() {
for (Observer* obs : observers) {
obs->update(state);
}
}
};
class Display : public Observer {
std::string name;
public:
Display(const std::string& n) : name(n) {}
void update(int value) override {
std::cout << name << " received: " << value << "\n";
}
};Métodos clave:
attach(): Registra un observador para recibir notificacionesdetach(): Elimina un observador de la lista de notificacionesnotify(): Itera a través de todos los observadores y llama a su métodoupdate()
Use Observer cuando los cambios en un objeto requieran actualizar otros sin un acoplamiento fuerte entre ellos.
Pruébalo tú mismo
#include <iostream>
#include <string>
#include "WeatherStation.h"
using namespace std;
int main() {
// Leer entradas
string display1Name, display2Name;
double temperature;
cin >> display1Name;
cin >> display2Name;
cin >> temperature;
// TODO: Crear un objeto WeatherStation
// TODO: Crear dos objetos TemperatureDisplay con los nombres de entrada
// TODO: Adjuntar ambos visores a la estación meteorológica
// TODO: Establecer la temperatura al valor de entrada
// (Ambos visores deberían imprimir sus mensajes)
// TODO: Desacoplar el primer visor
// TODO: Establecer la temperatura 5 grados por encima de la entrada
// (Solo el segundo visor debería imprimir)
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 empleados10Visión general de la STL
Visión general y filosofía de la STLContenedores de la STLIteradoresAlgoritmos de la STLFuntores y expresiones lambdaResumen - Frecuencia de palabras13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonFactory y Abstract FactoryPatrón BuilderPatrón ObserverPatrón Strategy2Gestió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