Mi granito de java: Observer

sábado, 11 de junio de 2011

Observer

Este patrón de diseño permite reaccionar a ciertas clases llamadas observadores sobre un evento determinado.
Es usado en programación para monitorear el estado de un objeto en un programa. Está relacionado con el principio de invocación implícita. La motivación principal de este patrón es su utilización como un sistema de detección de eventos en tiempo de ejecución. Es una característica muy interesante en términos del desarrollo de aplicaciones en tiempo real.

Debe ser utilizado cuando:
  • Un objeto necesita notificar a otros objetos cuando cambia su estado. La idea es encapsular estos aspectos en objetos diferentes permite variarlos y reutilizarlos independientemente.
  • Cuando existe una relación de dependencia de uno a muchos que puede requerir que un objeto notifique a múltiples objetos que dependen de él cuando cambia su estado.

Este patrón tiene un uso muy concreto: varios objetos necesitan ser notificados de un evento y cada uno de ellos deciden como reaccionar cuando esta evento se produzca.
Un caso típico es la Bolsa de Comercio, donde se trabaja con las acciones de las empresas. Imaginemos que muchas empresas estan monitoreando las acciones una empresa X. Posiblemente si estas acciones bajan, algunas personas esten interesadas en vender acciones, otras en comprar, otras quizas no hagan nada y la empresa X quizas tome alguna decisión por su cuenta. Todos reaccionan distinto ante el mismo evento. Esta es la idea de este patrón y son estos casos donde debe ser utilizado.

Diagrama UML


Subject: conoce a sus observadores y ofrece la posibilidad de añadir y eliminar observadores. Posee un método llamado attach() y otro detach() que sirven para agregar o remover observadores en tiempo de ejecución.
Observer: define la interfaz que sirve para notificar a los observadores los cambios realizados en el Subject.
SubjectConcreto: almacena el estado que es objeto de interés de los observadores y envía un mensaje a sus observadores cuando su estado cambia.
ObserverConcreto: mantiene una referencia a un SubjectConcreto. Almacena el estado del Subject que le resulta de interés. Implementa la interfaz de actualización de Observer para mantener la consistencia entre los dos estados.

 Ejemplo

Vamos a suponer un ejemplo de una Biblioteca, donde cada vez que un lector devuelve un libro se ejecuta el método devuelveLibro(Libro libro) de la clase Biblioteca.
Si el lector devolvió el libro dañado entonces la aplicación avisa a ciertas clases que están interesadas en conocer este evento:

Cada clase que quiera observar el cambio del estado en el libro deberá implementar la siguiente interface y darle lógica al método update:





Veamos el subject:


La biblioteca es quién dispara el evento. Seguramente el estado de un libro no estará en formato String, pero no viene al caso.


Veamos como funciona:


Consecuencias
  • Permite modificar las clases subjects y las observers independientemente.
  • Permite añadir nuevos observadores en tiempo de ejecución, sin que esto afecte a ningún otro observador.
  • Permite que dos capas de diferentes niveles de abstracción se puedan comunicar entre sí sin romper esa división.
  • Permite comunicación broadcast, es decir, un objeto subject envía su notificación a todos los observers sin enviárselo a ningún observer en concreto (el mensaje no tiene un destinatario concreto). Todos los observers reciben el mensaje y deciden si hacerle caso ó ignorarlo.
  • La comunicación entre los objetos subject y sus observadores es limitada: el evento siempre significa que se ha producido algún cambio en el estado del objeto y el mensaje no indica el destinatario.

Temas a tener en cuenta.

Si los observadores pueden observar a varios objetos subject a la vez, es necesario ampliar el servicio update() para permitir conocer a un objeto observer dado cuál de los objetos subject que observa le ha enviado el mensaje de notificación.
Una forma de implementarlo es añadiendo un parámetro al servicio update() que sea el objeto subject que envía la notificación (el remitente). Y añadir una lista de objetos subject observados por el objeto observer en la clase Observer.
Si los objetos observers observan varios eventos de interés que pueden suceder con los objetos subjects, es necesario ampliar el servicio add() y el update() además de la implementación del mapeo subject-observers en la clase abstracta Subject. Una forma de implementarlo consiste en introducir un nuevo parámetro al servicio add() que indique el evento de interés del observer a añadir e introducirlo también como un nuevo parámetro en el servicio update() para que el subject que reciba el mensaje de notificación sepa qué evento ha ocurrido de los que observa.

Cabe destacar que Java tiene una propuesta para el patrón observer:
Posee una Interfaz java.util.Observer: una clase puede implementar la interfaz Observer cuando dicha clase quiera ser informada de los cambios que se produzcan en los objetos observados. Tiene un servicio que es el siguiente: void update (Observable o, Object arg)
Este servicio es llamado cuando el objeto observado es modificado.
Además Java nos ofrece los siguientes servicios:
void addObserver (Observer o)
protected void clearChanged()
int countObservers()
void deleteObserver (Observer o)
void deleteObservers()
boolean hasChanged()
void notifyObservers()
void notifyObservers (Object arg)
protected void setChanged()

Posee una clase llamada java.util.Observable: esta clase representa un objeto Subject.
Veamos el mismo ejemplo con el estandard de Java:




14 comentarios:

Anónimo dijo...

Muchas gracias, me sirvio bastante, lo explicas de una manera muy apropiada.

Leojg dijo...

tengo una duda si es posible implementar observer desde otra interface.

se puede?

saludos.

Max dijo...

Muchas gracias por los comentarios. Leojg: mostre 2 formas distintas de implementar el observer: la primera con tus propias interfaces. En este caso tenes la libertad de usar las interfaces que quieras ya que las tenes que crear vos. En el 2do ejemplo, son las interfaces que nos da java. Ahi ya no tenes tanta libertad, salvo que hagas una herencia de interface o algo asi. No se si tu pregunta se referia a eso. Saludos, Maxi

Rodrigo dijo...

Estimado, muy buena la explicación, me ha sido de gran ayuda, y esta muy bueno que lo muestres de dos maneras.

John Ortiz Ordoñez dijo...

Gracias por tu trabajo. Me aclara algunos conceptos que tenían algo «enlagunados». Hasta pronto.

Max dijo...

De nada, me alegro que les guste, pronto volvere a publicar algunas cosas en el blog.

Anónimo dijo...

Excelente articulo, tengo una duda en el primer ejemplo, en la clase AlarmaLibro ¿por qué es necesario que sea static el arraylist?.
Gracias

Max dijo...

Es static porque la idea es que haya una sola alarma en la aplicacion. Lo ideal seria que la clase sea un Singleton pero no quise ensuciar el codigo metiendo patrones dentro de patrones ya que complicaria el aprendizaje.
Saludos!

Angel Zazueta Muñoz dijo...

Hey!!! chido chido tu código, me ayudó a entender mucho más cómo aplicarlo con mis propias interfaces y con las clases que Java ofrece.
Muuuchas gracias!!

Diego dijo...

Me gustó mucho el post . Sobre todo porque mostraste una implementación propia fuera de la que ofrece Java .
Una consulta , he leído que es válido en el patrón MVC que la vista "observe" los cambios en el modelo . Mi duda es la siguiente , estoy haciendo una aplicación en Swing, con las 3 capas del MVC , ¿podrìa utilizar este patrón para que cuando un DAO por ejemplo inserta correctamente algo, le notifique a una vista para que esta muestre un mensaje? . Actualmente siempre retornaba algún valor desde mi Model(DAO) -> Controller -> Vista, y de acuerdo a lo que me retornaba mostraba un mensaje en mi vista.
Un saludo desde Perú.

Max dijo...

Hola Diego para este caso no creo que sea una buena idea implementar este patron. Lo ideal es trabajar bien las excepciones. Cuando la vista/controlador llaman a modelo este debe devolver el objeto indicado. En caso que algo no salga bien, entonces deberia devolver una excepcion con un mensaje de error apropiado para que pueda mostrar la vista. En tu caso particular, por lo general los inserts suelen ser void. Calculo que con tener un metodo void que lanze una o mas excepciones seria suficiente.

Diego dijo...

Comprendido . Muchas gracias!. Espero que sigas actualizando el blog que está interesante .

Un saludo!

Anónimo dijo...

Me podrias ayudar con un ejemplo de un patron que se llama WatchDog

Furg dijo...

Entiendo que rompieran el libro "Windows es estable" hahaha Gracias crack!!