El patrón Observer

Los patrones de diseño son como soluciones precocinadas para problemas comunes en el diseño y desarrollo del software. Hay muchos problemas recurrentes al diseñar un programa, y los patrones de diseño nos pueden servir para adelantar unos pasos y no repetir trabajo.

Uno de estos patrones es el patrón Observer. Funciona como… como Apple, por ejemplo. Apple tiene miles de fanboys por el mundo expectantes a que saquen algún nuevo producto. Cuando Apple hace un lanzamiento, todos sus fanboys se enteran y cada uno actúa de manera distinta: Unos van a comprar el producto el mismo día del lanzamiento, otros esperan un par de meses a que pase el hype y otros se lamentan de no tener dinero para agenciarse el nuevo cacharro. Obviamente, Apple sólo se ha molestado de informar del nuevo producto, no tiene constancia de lo que va a hacer cada uno de los pringaos fanboys.

El patrón Observer funciona igual. Tenemos una clase Observable, que guarda un conjunto de referencias a unas clases Observadoras, y en un momento dado envía una notificación a todos los observadores. Cada una de las clases observadoras tendrá un método con una especificación común, pero en cada caso hará algo diferente, dependiendo de la clase.

Vamos a poner un pequeño ejemplo en C++.

Clases base

Primero definimos las clases bases Observer y Observable.

  1. #ifndef _PATRONOBSERVER_H_
  2. #define _PATRONOBSERVER_H_
  3.  
  4. #include <vector>
  5. using namespace std;
  6.  
  7. /// Base abstracta para las clases observadoras
  8. class Observer
  9. {
  10. public:
  11.     virtual void update (void *, void * = NULL) = 0;
  12. };
  13.  
  14. /// Base para la clase observable
  15. class Observable
  16. {
  17. public:
  18.     /// Conjunto de observadores
  19.     vector <Observer *> observers;
  20.  
  21.     /// Añade un nuevo observador al conjunto
  22.     inline void addObserver (Observer * a){  observers.push_back(a)}
  23.  
  24.     /// Notifica a todos los observadores
  25.     void notifyObservers(void * args = NULL){
  26.         for(vector <Observer *>::const_iterator iterador = observers.begin();
  27.             iterador != observers.end();
  28.             iterador++)
  29.         {
  30.             (*iterador)->update(this, args);
  31.         }
  32.     }
  33.  
  34. };
  35.  
  36. #endif /* _PATRONOBSERVER_H_ */
  37.  

Por un lado, la clase Observer tiene un método update que recibe dos punteros genéricos. El primero será siempre un puntero al objeto al que se está observando (el observable), mientras que el segundo puede estar vacío, o apuntar a alguna estructura de datos con información que nos interese notificar.

Por otro lado, la clase Observable guarda el conjunto de observadores en un vector de punteros, que se actualiza con el método addObserver (se podría implementar un removeObserver, obviamente). Por último tiene un método notifyObservers que puede recibir un puntero a algo, y que recorre el vector de observadores, notificándolos a todos (llamando a sus métodos update), y pasándoles el objeto de información, si es que lo hubiera.

Clases derivadas

  1. #ifndef _CLASES_H_
  2. #define _CLASES_H_
  3.  
  4. #include "patronObserver.h"
  5. #include <iostream>
  6. #include <iomanip>
  7.  
  8. using namespace std;
  9.  
  10. class lanzaNum : public Observable{
  11. public:
  12.     int max, actual;
  13.  
  14.     void set(int a, int b){
  15.         max = a; actual = b;
  16.         notifyObservers();
  17.     }
  18.     inline const int getMax() const{ return max;   }
  19.     inline const int getActual() const{ return actual; }
  20. };
  21.  
  22. class porcentaje : public Observer{
  23. public:
  24.     void update(void * L, void * arg = NULL){
  25.         lanzaNum * a = static_cast<lanzaNum *>(L);
  26.         cout << “Porcentaje: “
  27.              << ((float)a->getActual() / (float)a->getMax())*100
  28.              << “%” << endl;
  29.     }
  30. };
  31.  
  32. class intervalo : public Observer{
  33. public:
  34.     void update(void * L, void * arg = NULL){
  35.         int pasos = 10;
  36.         lanzaNum * a = static_cast<lanzaNum *>(L);
  37.         int aux = a->getActual() * pasos / a->getMax();
  38.         cout << “Intervalo: “;
  39.         cout << “["
  40.              << setfill('-') << setw(aux)
  41.              << "*"
  42.              << setw(pasos - aux + 1)
  43.              << right << "]“
  44.              << endl;
  45.     }
  46. };
  47.  
  48. #endif /* _CLASES_H_ */
  49.  

Tenemos una clase observable lanzaNum que guarda dos números: un máximo y un valor intermedio entre 0 y el máximo. Por otro lado, tenemos dos clases observadoras porcentaje e intervalo, que observan a lanzaNum. Cada una recibirá lo mismo: un puntero al objeto observable, pero cada una representará la información de distinta forma. Podemos hacer un pequeño programa de prueba:

  1. #include "clases.h"
  2.  
  3. int main(){
  4.     lanzaNum L;
  5.     porcentaje P;
  6.     intervalo I;
  7.    
  8.     L.addObserver(&P);
  9.     L.addObserver(&I);
  10.  
  11.     L.set(10,5);
  12. }

Y observamos que, al llamar al método set de la clase lanzaNum, ésta notifica a todos sus observadores I y P, ejecutando sus métodos update. En cada clase se representa la notificación de una manera distinta: Una con un porcentaje, y otra con un intervalo gráfico dibujado en consola.

Se puede ver que la utilidad del patrón Observer es grande. Se puede utilizar en cualquier caso en que podamos representar unos mismos datos de diferentes maneras, o en situaciones completamente distintas, como en las interfaces gráficas, en las que su uso es muy común, pero en lugar de llamarse Observers se les conoce como listeners, y escucha los diferentes eventos que se puedan generar en una GUI, tales como un click, una pulsación de una tecla, etcétera.

2 gañanes han comentado

  1. Mort on Julio 20th, 2008

    Merece la pena echar un vistazo al patrón Singleton, se combina muy bien con este ultimo.

    Un singleton es una clase de la cual solo puede existir una única instancia, es perfecta como objeto observable para garantizar la unicidad y coherencia de los datos.

    http://es.wikipedia.org/wiki/Singleton

    Pd: Buen articulo =)

  2. TheOm3ga on Julio 20th, 2008

    Gracias por comentar, Mort :) Tenía pensado escribir un artículo sobre el singleton también. La verdad es que lo que comentas de utilizar un singleton observable es super útil. A ver si me agencio un libro sobre patrones de diseño, porque no salgo del observer, singleton, y el MVC de oídas. Un saludo!

Leave a Reply