El patrón Observer representa uno de los mecanismos de diseño más utilizados en el desarrollo de software moderno. Su relevancia radica en la capacidad de establecer una comunicación eficiente entre componentes, permitiendo que un elemento central notifique automáticamente a múltiples dependientes cuando ocurre un cambio de estado. Este concepto forma la base estructural del famoso modelo Model/View/Control (MVC), ampliamente empleado en arquitecturas de aplicaciones empresariales.
En frameworks como MFC, la estructura Doc/View implementa los principios del MVC proporcionando una separación clara entre datos y presentación. De manera similar, frameworks web como Struts en Java utilizan esta misma filosofía. El propio lenguaje Java incluye interfaces nativas para ipmlementar este patrón, lo que evidencia su importancia fundamental en el diseño de sistemas desacoplados.
El problema que resuelve el patrón Observer es establecer una relación de dependencia uno a muchos entre objetos, donde el objeto principal (Subject) puede tener múltiples observadores interesados en sus cambios de estado. Cuando el estado del sujeto cambia, todos los observadores registrados reciben notificación automática.
Definición según el libro "Design Patterns: Elements of Reusable Object-Oriented Software"
Define una dependencia uno a muchos entre objetos, de manera que cuando un objeto cambia su estado, todos los objetos que dependen de él son notificados automáticamente. Esta interacción también se conoce como patrón publish-subscribe (publicar-suscribir). El sujeto emisor no necesita conocer la identidad de sus observadores.
Ejemplo práctico común
Consideremos un sistema de análisis de datos estadísticos que debe presentar la misma información en múltiples formatos: vista tabular, gráficos de barras, representaciones porcentuales, entre otras. Todas estas vistas dependen de un conjunto de datos común. Cuando los datos base se modifican, todas las representaciones deben actualizarse simultáneamente. El patrón Observer resuelve este problema de sincronización de manera elegante.
Estructura UML del patrón
El patrón se compone de cuatro elementos principales:
Subject (Sujeto): Mantiene una lista de observadores registrados y proporciona métodos para agregar o eliminar observadores. Conoce a todos sus observadores pero no depende de ninguna clase concreta de ellos.
Observer (Interfaz de观察者): Define el contrato que deben implementar todos los observadores, típicamente con un método de actualización que será llamado cuando el sujeto cambie de estado.
ConcreteSubject (Sujeto Concreto): Almacena el estado que interesa a los observadores y envía notificaciones a todos ellos cuando ocurre un cambio en su estado interno.
ConcreteObserver (Observador Concreto): Mantiene referencia al sujeto concreto y actualiza su estado interno para mantener consistencia con los cambios del sujeto.
Flujo de trabajo
Cuando el ConcreteSubject experimenta un cambio que puede afectar a sus observadores, les notifica automáticamente. Los observadores pueden entonces consultar al sujeto para obtener detalles adicionales y ajustar su propio estado según corresponda.
Escenarios de aplicación
El patrón Observer es apropiado cuando:
Existe una abstracción con dos aspcetos interdependientes que conviene separar en objetos independientes para permitir reutilización independiente.
Un objeto necesita modificar otros objetos relacionados pero no conoce cuántos objetos específicos serán afectados.
Un objeto debe notificar a otros sin establecer dependencias fuertes, favoreciendo el bajo acoplamiento entre componentes.
Primera implementación práctica:
#include <iostream>
#include <list>
using namespace std;
class ObservadorInterfaz
{
public:
virtual void Notificar(int) = 0;
virtual ~ObservadorInterfaz() {}
};
class SujetoInterfaz
{
public:
virtual void Suscribir(ObservadorInterfaz *) = 0;
virtual void Desuscribir(ObservadorInterfaz *) = 0;
virtual void NotificarObservadores() = 0;
virtual ~SujetoInterfaz() {}
};
class ObservadorConcretoA : public ObservadorInterfaz
{
public:
ObservadorConcretoA(SujetoInterfaz *pSujeto) : m_pSujeto(pSujeto){}
void Notificar(int valor) override
{
cout << "ObservadorConcretoA recibio notificacion. Nuevo valor: " << valor << endl;
}
private:
SujetoInterfaz *m_pSujeto;
};
class ObservadorConcretoB : public ObservadorInterfaz
{
public:
ObservadorConcretoB(SujetoInterfaz *pSujeto) : m_pSujeto(pSujeto){}
void Notificar(int valor) override
{
cout << "ObservadorConcretoB recibio notificacion. Nuevo valor: " << valor << endl;
}
private:
SujetoInterfaz *m_pSujeto;
};
class SujetoConcreto : public SujetoInterfaz
{
public:
void Suscribir(ObservadorInterfaz *pObservador) override;
void Desuscribir(ObservadorInterfaz *pObservador) override;
void NotificarObservadores() override;
void EstablecerEstado(int estado)
{
m_iEstado = estado;
}
int ObtenerEstado() const
{
return m_iEstado;
}
private:
std::list<ObservadorInterfaz *> m_ListaObservadores;
int m_iEstado;
};
void SujetoConcreto::Suscribir(ObservadorInterfaz *pObservador)
{
m_ListaObservadores.push_back(pObservador);
}
void SujetoConcreto::Desuscribir(ObservadorInterfaz *pObservador)
{
m_ListaObservadores.remove(pObservador);
}
void SujetoConcreto::NotificarObservadores()
{
std::list<ObservadorInterfaz *>::iterator it = m_ListaObservadores.begin();
while (it != m_ListaObservadores.end())
{
(*it)->Notificar(m_iEstado);
++it;
}
}
int main()
{
// Crear el sujeto concreto
SujetoConcreto *pSujeto = new SujetoConcreto();
// Crear observadores
ObservadorInterfaz *pObsA = new ObservadorConcretoA(pSujeto);
ObservadorInterfaz *pObsB = new ObservadorConcretoB(pSujeto);
// Modificar el estado interno
pSujeto->EstablecerEstado(42);
// Registrar observadores
pSujeto->Suscribir(pObsA);
pSujeto->Suscribir(pObsB);
// Notificar a todos los observadores
pSujeto->NotificarObservadores();
// Eliminar un observador
pSujeto->Desuscribir(pObsA);
// Cambiar estado y notificar nuevamente
pSujeto->EstablecerEstado(100);
pSujeto->NotificarObservadores();
// Liberar memoria
delete pObsA;
delete pObsB;
delete pSujeto;
return 0;
}
Segunda implementación: Sistema de notificaciones laborales
En este ejemplo, el sujeto gestiona el registro de observadores y proporciona la operación de notificación. Los observadores implementan un método de actualización que se ejecuta cuando el sujeto emite la notificación, no inmediatamente cuando cambia el estado.
#include <iostream>
#include <string>
#include <list>
using namespace std;
class SujetoBase;
//Interfaz abstracta para observadores
class Observador
{
protected:
string nombre;
SujetoBase *sujeto;
public:
Observador(string nombre, SujetoBase *sujeto)
{
this->nombre = nombre;
this->sujeto = sujeto;
}
virtual void actualizar() = 0;
virtual ~Observador() {}
};
//Observador concreto para seguimiento de bolsa
class ObservadorBolsa :public Observador
{
public:
ObservadorBolsa(string nombre, SujetoBase *sujeto) :Observador(nombre, sujeto)
{
}
void actualizar() override;
};
//Observador concreto para seguimiento de deportes
class ObservadorDeportes :public Observador
{
public:
ObservadorDeportes(string nombre, SujetoBase *sujeto) :Observador(nombre, sujeto)
{
}
void actualizar() override;
};
//Interfaz abstracta para el sujeto
class SujetoBase
{
protected:
list<Observador*> listaObservadores;
public:
string accion;
virtual void agregar(Observador*) = 0;
virtual void eliminar(Observador*) = 0;
virtual void notificar() = 0;
virtual ~SujetoBase() {}
};
//Sujeto concreto: Secretaria
class Secretaria :public SujetoBase
{
public:
void agregar(Observador *obs) override
{
listaObservadores.push_back(obs);
}
void eliminar(Observador *obs) override
{
list<Observador *>::iterator iter = listaObservadores.begin();
while (iter != listaObservadores.end())
{
if ((*iter) == obs)
{
listaObservadores.erase(iter);
break;
}
++iter;
}
}
void notificar() override
{
list<Observador *>::iterator iter = listaObservadores.begin();
while (iter != listaObservadores.end())
{
(*iter)->actualizar();
++iter;
}
}
};
void ObservadorBolsa::actualizar()
{
cout << nombre << " recibio la notificacion: " << sujeto->accion << endl;
if (sujeto->accion == "El jefe ha llegado!")
{
cout << "Cierro inmediatamente la aplicacion de bolsa y parezco muy ocupado." << endl;
}
}
void ObservadorDeportes::actualizar()
{
cout << nombre << " recibio la notificacion: " << sujeto->accion << endl;
if (sujeto->accion == "El jefe ha llegado!")
{
cout << "Cierro inmediatamente el partido y parezco muy ocupado." << endl;
}
}
int main()
{
//Crear el sujeto concreto
SujetoBase *oficina = new Secretaria();
//Crear observadores
Observador *empleado1 = new ObservadorDeportes("Juan", oficina);
Observador *empleado2 = new ObservadorDeportes("Maria", oficina);
Observador *empleado3 = new ObservadorBolsa("Pedro", oficina);
//Registrar observadores
ofuscina->agregar(empleado1);
oficina->agregar(empleado2);
oficina->agregar(empleado3);
//Simular evento
oficina->accion = "Hora de almorzar!";
oficina->notificar();
cout << endl;
//Simular otro evento
oficina->accion = "El jefe ha llegado!";
oficina->notificar();
delete empleado1;
delete empleado2;
delete empleado3;
delete oficina;
return 0;
}
Este segundo ejemplo ilustra un escenario común en entornos laborales donde un secretario (Secretaria) actúa como sujeto que notifica a los empleados cuando ocurre algún evento importante, como la llegada del jefe o announcements corporativos.