El patrón Observer en Java I

El patrón Observer en Java I

En la entrada del patrón observer, terminábamos con una pregunta.

¿Cómo podemos controlar cuándo se envían las notificaciones?

La respuesta es simple: añadiendo un estado al sujeto que envía las notificaciones mediante una variable. Si esta variable tiene un valor, notificamos a los observadores y la devolvemos a su estado original. En caso contrario no hacemos nada.

Por ejemplo, supongamos que la clase Balanza notifica a sus observadores cuando se ha puesto un peso sobre ella de más de 1000gr. Si no tuviéramos esa variable booleana en cada pesaje tendríamos que recorrer toda la lista, notificando de cualquier pesaje.

Podrías pensar acertadamente que existe cierta complicación en el código. ¿Por que no notificar directamente si se da una condición a los observadores? Bueno, en este ejemplo quizás si sería lo más lógico, pero sirve como excusa para extrapolar a otras situaciones donde las condiciones para notificar sean más complejas.

Aunque controlar las notificaciones no es algo contemplado en el propio patrón observer, la implementación de Java sí que añade por defecto esta posibilidad. Si echamos un vistazo al código de la clase Observable vemos que tiene dos campos: un Vector de objetos Observer y un atributo booleano.

Una herencia envenenada

Que Observable tenga una clase Vector para albergar los Observer puede ser indicativo de una necesaria huida. Pero aprovechemos que estamos aquí para hacer énfasis en el punto clave de esta implementación:

  • Observable es una clase concreta.

Esto implica que para hacer tu patrón Observer con la implementación de Java tienes una limitación: Tu objeto debe heredar de Observable, y si ya extiende otro objeto, no puedes hacer nada. ¿Que pasa si quieres customizar tu objeto Observable? Sigues teniendo que extender la clase. Y si quieres componer un objeto con otro que extienda Observable, olvídate de usar sus métodos para cambiar y leer el estado porque son protected.

Por supuesto esto tiene una explicación y es que estas dos clases, tal y como podéis ver en su documentación, existen desde el JDK 1.0. Por este y otros motivos se recomienda implementar tu propio patrón Observer a través de interfaces.

Puedes encontrar un ejemplo sencillo y comentado de la implementación del patrón observer en mi repositorio de Github.

 

El patrón Observer

El patrón Observer

El patrón Observer es posiblemente el más conocido de los patrones de diseño de software, quizás porque es el común denominador de cualquier aplicación gráfica.

Es muy fácil de explicar y se puede resumir en que cuando un objeto A le pasa algo, este se lo notifica a una serie de objetos llamados observadores. Quizás lo difícil sea implementarlo, pero vayamos por partes.

Según la definición tenemos dos tipos de agentes: un sujeto del cambio y un observador y por lo tanto tendremos dos clases: un Sujeto y un Observador.

Sabemos también que el sujeto debe notificar a un observador. Para ello haremos que el sujeto guarde en una lista a todos sus observadores.

¿Pero como lo notifica? Pues simplemente implementaremos un método en los observadores que sea llamado por el objeto observado mediante un bucle foreach.

Pero esto se puede mejorar…¿Como? El comportamiento que hemos visto es extensible para cualquier objeto sujeto a cambio y a sus observadores, por lo tanto, podemos abstraerlo en dos interfaces.

Esta generalización nos permite tener varios observadores u oyentes que hagan distintas cosas cuando el sujeto quiera notificar algo. Hemos desacoplando ambos objetos y ahora el sujeto ya no tiene que saber de qué tipo concreto es su oyente para notificarle un cambio.

¿Pero qué pasa con los observadores?

Como a lo mejor te habrás dado cuenta este diseño deja un poco desvalidas a las clases observadoras.  No controlan cuando deben dejar de serlo de una clase ni tienen control ninguno sobre cuándo quieren ser notificadas. No nos preocupemos, si queremos que las clases observadoras tengan el control de suscribirse y desuscribirse a eventos, solo necesitaremos hacer implementar una referencia a la clase que está observando para notificarle sus deseos.

¿Y si una clase solo quiere ser notificada en determinadas circunstancias? Bueno, aprovecharemos para explicar eso mientras vemos la implementación nativa de Java para este patrón en otra entrada.

El patrón Strategy

El patrón Strategy

l patrón Strategy puede dar lugar a confusión por culpa de su nombre la primera vez que nos enfrentamos a él. A diferencia de otros patrones no deja muy claro cuál es su propósito, por ese motivo en mi cabeza cada vez que resuena el nombre de este patrón lo asocio con otra palabra: «comportamiento».

Muchos de vosotros y vosotras habrán comenzado el el mundo de la programación orientada a objetos aprendiendo a implementar funcionalidades mediante la sacrosanta herencia, es decir, mediante la generalización de un comportamiento. Si todos los animales hacen sonidos, crearemos una clase Animal (a poder ser abstracta) que permita la escritura del método <code>sonido()</code> en las clases hijas. Esto es lo primero que nos enseñan y hasta que no comenzamos a estudiar los patrones de diseño, creemos que todo se puede solucionar extendiendo clases e implementando interfaces. Para un martillo todo son clavos y conocer patrones de diseño te permite no apretar una tuerca a golpes.

El eterno ejemplo de los animales y sus sonidos con el que todas y todos nos hemos criado en herencia presenta algunos problemas cuando nos planteamos las siguientes dos preguntas.

  • ¿Que pasa si no todos los animales ejecutan un sonido?

Podríamos sobrescribir el método sin ningún código (nada elegante ni práctico) o bien crear una interfaz que solo implementaran los animales que generasen sonidos.

Sin embargo la primera opción plantea el problema de pedazos de código inútil, y si hacemos esto en esquemas de objetos relativamente grandes, pronto nos encontraremos con una ingente cantidad de métodos inútiles en clases que no deberían tener prácticamente código, dificultando su lectura y mantenimiento.

La segunda opción parece la clásica. Yo elijo qué objetos implementan qué comportamiento, pero… ¿y el código repetido? ¿Te has planteado lo que es poner System.out.println(«Guau») en el medio centenar de clases que heredan de perro?¿Y lo que supondría modificar el comportamiento de todas ellas en un futuro?

Si eres conocedor del JDK8 podrías decir «ahora las interfaces pueden implementar códigos por defecto» como se explica aqui, pero obviemos esa posibilidad porque quiero seguir pensando que en Java la herencia múltiple no existe.

  • ¿Qué pasa si quiero que un sonido cambie en tiempo de ejecución?

Querido, lector, ni siquiera el JDK8 te da solución a este problema. Necesitas implementar definitivamente el patrón Strategy.

Entremos pues en la respuesta que da el patrón Strategy al problema:

Insertar un atributo que defina el comportamiento del objeto y que sea polimórfico.

Esto se ve mucho mejor con un diagrama de clases:

La clase Perro contiene un objeto en el que confía su comportamiento (en este caso emitir un sonido).

Cuáles son las ventajas de esto?

  1. Un comportamiento mutable en tiempo de ejecución.
  2. Mejoras tanto en mantenimiento como extensibilidad. Evitamos repetir código y nos permite hilar más fino en un futuro.

Veamos un ejemplo de la facilidad con la que podemos cambiar el comportamiento de un objeto Perro del anterior diagrama.

Como veis, resulta extremadamente sencillo variar el comportamiento de una clase mediante un patrón nada difícil de implementar.

Podéis ver otro ejemplo de este patrón junto con varios otros en mi repositorio de Github.