Design Patterns - Observer

Propósito

El patrón Observer (Observador) establece una suscripción en la que los objetos pueden observar, y ser notificados de los cambios que ocurren en otros objetos, permitiendo así una comunicación dinámica y acoplamiento flexible entre ellos.

El observador se suscribe al sujeto. Cuando el sujeto cambia su estado, notifica al observador (y a todos los observadores suscriptos) que su estado cambió.

Problema

El patrón Observer trata los siguientes problemas:

Acoplamiento entre Sujeto y Observadores: Reduce el acoplamiento entre el objeto que emite una notificación y los objetos que reciben la notificación.

Notificaciones de Cambios de Estado: Facilita la comunicación cuando un objeto necesita ser monitoreado por uno o más objetos.

Solución

El patrón Observer propone las siguientes soluciones:

Suscripción: Permite a los observadores suscribirse y desuscribirse a las notificaciones del sujeto.

Notificación Automática: El sujeto notifica automáticamente a todos los observadores cuando su estado cambia.

Estructura

observer

Participantes

  • Subject: Interfaz o clase abstracta que define métodos para registrar, desregistrar y notificar observers.
  • ConcreteSubject: Almacena el estado de interés y envía una notificación a sus observers cuando su estado cambia.
  • Observer: Interfaz que define la operación de actualización invocada por el Subject.
  • ConcreteObserver: Observa al ConcreteSubject y reacciona a los cambios.

Cuándo Usarlo

Este patrón es recomendable cuando:

  • un cambio en un objeto requiere cambios en otros, y no se sabe cuántos objetos necesitarán ser cambiados.
  • los objetos que deben ser notificados varían o pueden variar en el tiempo.

Ventajas

Desacoplamiento: Permite que los sujetos y los observadores operen independientemente.

Flexibilidad y Reutilización: Facilita la adición de nuevos observadores sin modificar el sujeto.

Notificaciones Automáticas: Los cambios en el sujeto se propagan automáticamente a todos los observadores.

Desventajas

Potencial de Actualizaciones Innecesarias: Los observadores pueden recibir notificaciones en las que no están interesados, lo que puede llevar a ineficiencias.

Gestión de Memoria: Si los observadores no se desvinculan adecuadamente, puede haber problemas de gestión de memoria.

Rendimiento: En sistemas con muchos observadores o actualizaciones frecuentes, el rendimiento puede verse afectado.

Ejemplo: Sistema de Alerta Meteorológica

Debemos desarrollar un sistema de alerta meteorológica destinado a proveer actualizaciones en tiempo real sobre las condiciones climáticas a varios dispositivos y usuarios.

Este sistema será utilizado por aplicaciones móviles, estaciones meteorológicas locales, servicios de noticias y otros sistemas interesados en datos meteorológicos precisos y actualizados.

Problema

El principal desafío es que el sistema pueda notificar eficientemente a todos los dispositivos y servicios suscriptores sobre cambios en las condiciones climáticas, sin tener que adaptarse a las especificidades de cada suscriptor.

Además, queremos que nuestro sistema sea escalable a la adición de nuevos suscriptores en el futuro, sin requerir una reestructuración significativa.

Solución planteada

Vamos a usar el patrón Observer porque ofrece una solución elegante para mantener a múltiples suscriptores informados sobre los cambios del sujeto.

Con este patrón, nuestro sistema meteorológico puede operar como un “Sujeto” que mantiene una lista de suscriptores, u “Observers”, y los notifica automáticamente sobre cualquier cambio de estado.

Esto nos permite agregar nuevos tipos de dispositivos y suscriptores sin alterar el núcleo de nuestra lógica de notificación, manteniendo el sistema flexible y fácil de mantener.

Definimos las interfaces Observer y Subject. Creamos el SubjectConcreto para manejar los datos del clima, ante cada cambio va a notificar a los observadores. Creamos dos observadores MobileApp y WeatherStation, que se van a suscribir al sujeto para estar al tanto de las novedades del clima.

observer

Código Java

Codificamos en Java lo que preparamos en el diagrama.

Definimos la interfaz Subject:


              
  interface Subject {
      void registerObserver(Observer o);
      void removeObserver(Observer o);
      void notifyObservers();
  }
              
              

Definimos la interfaz el Observer:


              
  interface Observer {
      void update(float temperature, float humidity, float pressure);
  }
              
              

Creamos la clase ConcreteSubject:


              
  class WeatherData implements Subject {
      private List observers;
      private float temperature;
      private float humidity;
      private float pressure;
  
      public WeatherData() {
          observers = new ArrayList<>();
      }
  
      public void registerObserver(Observer o) {
          observers.add(o);
      }
  
      public void removeObserver(Observer o) {
          observers.remove(o);
      }
  
      public void notifyObservers() {
          for (Observer observer : observers) {
              observer.update(temperature, humidity, pressure);
          }
      }
  
      public void measurementsChanged() {
          notifyObservers();
      }
  
      public void setMeasurements(float temperature, float humidity, float pressure) {
          this.temperature = temperature;
          this.humidity = humidity;
          this.pressure = pressure;
          measurementsChanged();
      }
  }
  
              

Implementamos los ConcreteObservers:


              
  class MobileApp implements Observer {
      private float temperature;
      private float humidity;
      private Subject weatherData;
  
      public MobileApp(Subject weatherData) {
          this.weatherData = weatherData;
          weatherData.registerObserver(this);
      }
  
      public void update(float temperature, float humidity, float pressure) {
          this.temperature = temperature;
          this.humidity = humidity;
          display();
      }
  
      public void display() {
          System.out.println("Mobile App: Temp=" + temperature + " Humidity=" + humidity);
      }
  }
              
  class WeatherStation implements Observer {
      private float temperature;
      private float humidity;
      private Subject weatherData;
  
      public WeatherStation(Subject weatherData) {
          this.weatherData = weatherData;
          weatherData.registerObserver(this);
      }
  
      public void update(float temperature, float humidity, float pressure) {
          this.temperature = temperature;
          this.humidity = humidity;
          display();
      }
  
      public void display() {
          System.out.println("Weather Station: Temp=" + temperature + " Humidity=" + humidity);
      }
  }
  
              

El código cliente:


              
  public class MeteorologicalService {
      public static void main(String[] args) {
          WeatherData weatherData = new WeatherData();

          MobileApp userApp = new MobileApp(weatherData);
          WeatherStation localStation = new WeatherStation(weatherData);

          weatherData.setMeasurements(28.3f, 65f, 1013.1f);
          weatherData.setMeasurements(26.5f, 70f, 1012f);
      }
  }
              
              

Mapeo (del ejemplo a Participantes)

Los participantes que vimos antes son: Subject, ConcreteSubject, Observer, ConcreteObserver:

  • Subject (Subject): Interface del sujeto a observar.
  • WeatherData (ConcreteSubject): Notifica a los observers sobre cambios en los datos meteorológicos.
  • Observer (Observer): Interface implementada por todos los observers.
  • MobileApp, WeatherStation (ConcreteObserver): Observadores concretos que reaccionan a los cambios en WeatherData.
  • MeteorologicalService (Client): Configura los observers y los actualiza con nuevos datos meteorológicos

Conclusiones

En este ejemplo pudimos ver en uso el patrón Observer.

Los observadores se registran en el sujeto y se actualizan automáticamente con nuevas mediciones, dando flexibilidad para agregar nuevos observadores.

Patrones relacionados

  • Mediator

Se puede usar junto con Observer para simplificar las comunicaciones entre objetos complejos.

  • Singleton

Se puede usar para garantizar un único sujeto o un único observador central en el sistema.