Patrones: Observer (Java/Python)


A menudo necesitamos realizar operaciones cuando ocurren eventos. En este tipo de situaciones existen dos soluciones: inyectar el objeto que tiene que recibir los eventos en el que los provoca o implementar el patrón Observer ("Observador", pero es raro que alguien traduzca los nombres de los patrones).

También es posible que un objeto genere eventos pero sean varios los consumidores de éstos. En swing, gtk,... ocurre constantemente, utilizando los conocidos listeners. Pero... ¿Qué es un listener realmente? No es más que una implementación del patrón Observer.

Veremos cómo utilizar este utilísimo patrón en Java y en Python.

El problema.

Veamos... Tenemos algo muy importante que realizar en cuando ocurre un evento, por ejemplo, escribir por pantalla "Something happened!". Nuestro evento va a ser que han pasado 100 ms.

Está claro que vamos a necesitar un hilo. El hilo será un "Observable", es decir, un objeto que genera eventos. En el hilo principal crearemos un "Observer", un objeto que realiza acciones cuando ocurren los eventos.

Veremos primero la solución en Java y después, en Python:

Soluciones

Java

Comenzaremos por el programa principal, que nos servirá como declaración de intenciones:

.. code:: java

/* file: org/magmax/patterns/observer/Main.java */
package org.magmax.patterns.observer;


public class Main {
    public static void main(String args[]) throws InterruptedException {
        MyObservable myobservable = new MyObservable();
        MyObserver myobserver = new MyObserver();

        myobservable.addObserver(myobserver);

        Thread thread = new Thread(myobservable);
        thread.start();

        Thread.sleep(2000);
        thread.stop();
        System.out.println("Finishing!");
    }
}

Poco que explicar aquí: declaramos nuestro observable y nuestro observer y asociamos el observer al observable, lanzamos un hilo con el observable y esperamos un rato. Al final imprimimos que hemos terminado.

El observer se encuentra en el hilo principal; veamos lo que hace:

.. code:: java

/* file org/magmax/patterns/observer/MyObserver.java */
package org.magmax.patterns.observer;

import java.util.Observable;
import java.util.Observer;

public class MyObserver implements Observer {

    @Override
    public void update(Observable observable, Object event) {
        System.out.println("Something happened!");
    }   
}

Tan solo imprime la cadena "Something happened!". Sin embargo aquí hay algunas cosas importantes: para empezar, está implementando la interfaz Observer. Ésta interfaz nos la proporciona Java en su librería estándar, y nos obliga a implementar el método update(Observable, Object). Éste es el método al que se llamará, dentro del hilo que creó el objeto, cuando ocurra un evento en el observable.

Y veamos cómo queda el observable:

.. code:: java

/* file org/magmax/patterns/observer/MyObservable.java */
package org.magmax.patterns.observer;

import java.util.Observable;

public class MyObservable extends Observable implements Runnable {

    public void fire_event() {
        this.notifyObservers("Ey! What's up?");
        this.setChanged();
    }

    @Override
    public void run() {
        while (true) {
            fire_event();
            sleep();
        }
    }

    private void sleep() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

Éste archivo es más largo, pero sólo por la verbosidad de Java. Veamos primero el método sleep, que lo único que hace es dormirse durante 100 milisegundos. Una vez ignorado este método, queda algo muy pequeño.

Lo primero en lo que debemos fijarnos es en la clase padre: Observable. Nuevamente, la librería estándar de Java nos ofrece herramientas para crear el patrón Observador. La clase Observable nos ofrece métodos que utilizamos en el programa principal, como addObserver, pero también algunos que necesitaremos en el Observable, como son notifyObservers(Object), que genera un evento, y setChanged(), que indica que hay cambios que publicar.

El evento disparado es la cadena "Ey! What's up?" que llegará al Observer. Podéis mejorar el ejercicio imprimiendo el evento y comprobando así que lo que digo es cierto XD

Además, implementa la interfaz Runnable, pero eso sólo es para trabajar con hilos. Esta interfaz nos obliga a implementar run(), que es el bucle de eventos que disparará un evento y se dormirá hasta que el hilo muera.

Hay cosas mejorables en este código (llamar el método obsoleto stop() de un hilo está muy feo), pero creo que queda claro el patrón Observador, que era la intención del artículo.

Python

Mientras Java nos proporciona herramientas para gestionar un patrón Observer, Python no lo hace. Así que tendremos que implementárnoslo nosotros. Voy a utilizar nombres similares a la implementación en Java y así podéis haceros una idea de lo que ocurre por debajo :D

.. code:: python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#!/usr/bin/python
# file: observer.py

from threading import Thread
import time


class Observable(object):
    def __init__(self):
        self._observers = set()

    def add_observer(self, observer):
        self._observers.add(observer)

    def remove_observer(self, observer):
        self._observers.remove(observer)

    def notify_observers(self, event):
        for observer in self._observers:
            observer.update(self, event)


class Observer(object):
    def update(self, observable, event):
        raise NotImplemented('This method is Abstract!')


class MyObservable(Thread, Observable):
    def __init__(self, *args, **kargs):
        Thread.__init__(self, *args, **kargs)
        Observable.__init__(self, *args, **kargs)
        self._finish = False

    def run(self):
        while not self._finish:
            self.fire_event()
            time.sleep(0.1)

    def fire_event(self):
        self.notify_observers("Ey! What's up?")

    def stop(self):
        self._finish = True


class MyObserver(Observer):
    def update(self, observable, event):
        print "Something happened!"


def main():
    myobservable = MyObservable()
    myobserver = MyObserver()

    myobservable.add_observer(myobserver)

    myobservable.start()
    time.sleep(2)
    myobservable.stop()

    print("Finishing!")

if __name__ == '__main__':
    main()

Lo primero que hago es implementarme yo mi Observer y mi Observable. Así ya sólo tengo que heredar de ellos. Como veis, no es nada difícil: cuando se registra un Observer, lo guardo en un set; cuando se lanza un evento, notifico a todos los Observers registrados en el Observable.

Realmente, la clase Observer no es necesaria en absoluto, ya que Python tiene duck typing, pero queda mono y así se parece más a Java.

El resto es igual que en Java :D

Problemas

Venga, comencemos con los problemas: En Java, Observable es una clase abstracta, por lo que no podemos usarla si tenemos que heredar de otra cosa. En Python esto no es un problema, gracias a la herencia múltiple, como podéis ver en el ejemplo de arriba.

Debo decir que los ejemplos mostrados no dejan de ser ejemplos y que son un poco guarros. Lo suyo es utilizar un objeto diferente como "token" que se pasan el Observer y el objeto que generará los eventos. Hacer que un objeto sea un Observable además de hacer alguna otra cosa... Viola el principio de única responsabilidad(SRP). Y claro, lo viola tanto en Java como en Python XD

Así que no quiere decir que, porque podamos usar la herencia múltiple e implementar un Observable junto con otro objeto no significa que debamos hacerlo. A las malas, si la necesidad es muy imperiosa, siempre podemos implementárnoslo a mano (seguirá violando el SRP)

No ocurre lo mismo con el Observer, ya que éste no tiene una responsabilidad, sino una necesidad: la de enterarse de los eventos que ocurran.

Más información

Podéis encontrar más información en la wikipedia, que curiosamente también lo muestra en Java y Python, con implementaciones bastante parecidas XD (os aseguro que lo encontré después), aunque con una explicación mucho más detallada.

Y, por supuesto, el Design Patterns, del GoF.


Comentarios

Comments powered by Disqus