The Observer Pattern

Comments

En este articulo, presentaremos otros de los patrones muy frecuentemente utilizado. El patrón Observer (Observador) permite notificar a los subscriptores si se han producido cambios en un objeto.

The Observer Pattern

Introducción

En este capitulo, presentaremos otros de los patrones muy frecuentemente utilizado por los desarrolladores. El patrón Observer (Observador) permite notificar a los subscriptores si se han producido cambios en un objeto.

GPS City Visit

Una agencia de turismo quiere proporcionar a los usuarios una aplicación GPS, para brindarle información sobre diferentes entidades de la ciudad. El visitante, desde un dispositivo colocado en su vehículo particular, debería ser informado automáticamente cada vez que visita una ciudad, sobre el clima local, los lugares de esparcimiento, las noticias más destacadas de la ciudad, entre otros factores.

Nos piden a nosotros, desarrollar la aplicación. Permitiendo añadir a futuro, nuevas entidades. Actualmente la agencia cuenta con información sobre el clima, las noticias destacadas de la ciudad y la oferta hotelera existente proveniente de una cadena muy reconocida.

Nosotros podríamos pensar en elaborar una clase como la siguiente:

/

Cada vez que la aplicación ingresa a una nueva ciudad, se invocaría al método visit() pasándole como parámetro la nueva ciudad. El método updateCity(City city); lo que hace es actualizar las diferentes entidades con la nueva ciudad.

Una implementación que podemos diseñar, es la siguiente:

public class GpsCity {

        //some fields data;

        public void updateCity(City newCity)
        {
            city = newCity;

            sHotel = new SystemHotel(city);

            weather = new Weather(city);

            featureNews = new News(city);
        }

        public void visit(City newCity)
        {
            updateCity(newCity);

            getDisplayWelcome();

            getSystemHotel();

            getWeather();

            getFeatureNews();

        }

        //other methods..
    }

Los métodos get obtienen información de sus respectivas entidades. No conocemos la implementación de las clases de las entidades, solo conocemos que es lo que hacen y como obtener la información pertinente.

El problema con esta primer versión radica en que, si deseamos añadir una nueva entidad, debemos reestructurar nuestra aplicación, por lo que se pierde flexibilidad.

Además, el código actual no nos permite modificar en tiempo de ejecución, que entidades queremos obtener. El sistema nos obliga a mostrar la información de todas las entidades sin poder realizar un filtrado.

Vemos que en el evento updateCity(), crea todas las entidades y asocia a cada una de ellas, la ciudad visitada actual. Como observamos, todas las entidades tienen una interfaz en común.

Es hora de poner en práctica el uso del patrón Observador (Observer Pattern) en nuestra aplicación.

Definición del patrón observador

Cuando usted está tratando de imaginar el patrón de observador, un servicio de suscripción a un periódico con su editor y los suscriptores es una buena manera de visualizar el patrón. Sin embargo, en el mundo real, normalmente verás el patrón de observador definido de esta manera:

Definción

El patrón observador define una dependencia de uno a muchos entre los objetos, de modo que cuando un objeto cambia de estado, todos sus dependientes son notificados y actualizados automáticamente.

Relacionemos esta definición con la forma en que hemos estado hablando sobre el patrón:

El sujeto y los observadores definen la relación uno a muchos. Los observadores dependen del tema, de modo que cuando el estado del sujeto cambia, los observadores reciben una notificación. Dependiendo del estilo de notificación, el observador también puede actualizarse con nuevos valores. Como descubrirá, hay algunas formas diferentes de implementar el patrón de observador, pero la mayoría gira en torno a un diseño de clase que incluye interfaces de sujeto y observador. Vamos a ver...

/

El poder del bajo acoplamiento

Cuando dos objetos están ligeramente acoplados, pueden interactuar, pero tienen muy poco conocimiento el uno del otro. El Patrón observador proporciona un diseño de objeto donde los sujetos y los observadores están acoplados de manera flexible.

¿Por qué?

Lo único que el sujeto sabe sobre un observador es que implementa una determinada interfaz (la interfaz de observador). No necesita saber la clase concreta del observador, lo que hace, o cualquier otra cosa al respecto.

Podemos añadir nuevos observadores en cualquier momento. Debido a que el sujeto depende solo de una lista de objetos que implementan la interfaz de observador, podemos agregar observadores nuevos cuando queramos. De hecho, podemos reemplazar cualquier observador en tiempo de ejecución con otro observador y el sujeto seguirá funcionando. Asimismo, podemos eliminar observadores en cualquier momento.

Nunca necesitamos modificar al sujeto para agregar nuevos tipos de observadores. Digamos que tenemos una nueva clase concreta que debe ser un observador. No necesitamos realizar ningún cambio en al sujeto para adaptarse al nuevo tipo de clase, todo lo que tenemos que hacer es implementar la interfaz de observador en la nueva clase y registrarse como observador. Al sujeto no le importa; entregará notificaciones a cualquier objeto que implemente la interfaz de observador.

Podemos reutilizar sujetos u observadores independientemente unos de otros. Si tenemos otro uso para un sujeto o un observador, podemos reutilizarlos fácilmente porque los dos no están estrechamente acoplados. Los cambios en el sujeto o en un observador no afectarán al otro. Debido a que los dos están acoplados libremente, somos libres de realizar cambios en cualquiera de los dos, siempre y cuando los objetos sigan cumpliendo con sus obligaciones de implementar las interfaces del sujeto o del observador.

Los diseños poco acoplados nos permiten construir sistemas OO flexibles que pueden manejar el cambio porque minimizan la interdependencia entre los objetos.

Diseñando la aplicación GPS City

/

Implementando la aplicación GPS City.

Vamos a comenzar nuestra implementación usando el diagrama de clases. Más adelante en este capítulo veremos que java proporciona algún soporte incorporado para el patrón Observer, sin embargo, vamos a ensuciarnos las manos y escribir las nuestras por ahora. Mientras que en algunos casos puedes usar el soporte incorporado de Java, en muchos casos es más flexible construir el tuyo (y no es tan difícil). Entonces, comencemos con las interfaces:

public interface Observer {

        public void update(City city);
    }

La interfaz Observer es implementada por todos los observadores, ellos tienen que implementar el método update(). Aquí le pasamos la ciudad actual a los observadores.

public interface Subject {

            public void registerObserver(Observer o);
            public void unregisterObserver(Observer o);

            public void notifyObserver();

        }

Los métodos registerObserver() y unregisterObserver() obtienen un Observer como argumento; que es el Observer a ser registrado o eliminado.

El método notifyObserver() es llamado para notificar a todos los observadores cuando el estado del sujeto ha sido cambiado.

public interface DisplayElement {

            public void display();

        }

Ahora vamos a implementar las clases correspondientes.

public class GpsCity implements Subject{

            private ArrayList observers;

            public City city;

            public GpsCity(){
                observers = new ArrayList();
            }

            public void updateCity(City newCity){

                city = newCity;

            }

            public void visit(City newCity)
            {

                updateCity(newCity);

                getDisplayWelcome();

                notifyObserver();

            }

            public City getCity()
            {
                return city;
            }

            public void getDisplayWelcome() {
                System.out.println("Visitando... " + city.getName());
            }

            @Override
            public void registerObserver(Observer o) {
                observers.add(o);
            }

            @Override
            public void unregisterObserver(Observer o) {

                int i = observers.indexOf(o);
                if (i>=0){
                    observers.remove(i);
                }

            }

            @Override
            public void notifyObserver() {

                for(int i = 0; i < observers.size(); i++){
                    Observer observer = (Observer) observers.get(i);
                    observer.update(city);
                }
            }
        }

En Java, para poder utilizar la clase ArrayList debe importarla añadiendo en la cabecera import java.util.ArrayList;

public class City {

        private String name;

        public City(String newCityName)
        {
            name = newCityName;
        }

        public String getName() {
            return name;
        }

    }
public class News implements Observer, DisplayElement {

        private City city;

        public News(Subject gpsCity) {

            gpsCity.registerObserver(this);

        }

        public void getFeaturedNews() {
            System.out.println("Estas son las noticias descatadas para " + city.getName());
        }

        @Override
        public void update(City city) {
            this.city = city;
            display();
        }

        @Override
        public void display() {
            getFeaturedNews();
        }

    }
public class Weather implements Observer, DisplayElement {

        private City city;

        public Weather(Subject gpsCity)
        {
            gpsCity.registerObserver(this);
        }

        public void show() {
            System.out.println("Tiempo para la ciudad de " + city.getName());
        }

        public void update(City city) {
            this.city = city;
            display();
        }

        public void display() {
            show();
        }

    }

Ahora implementaremos un test para comprobar la funcionalidad de la aplicación.

public class GgsCityTest {
        public static void main(String[] args)
        {
            GpsCity gpscity = new GpsCity();

            Weather weather = new Weather(gpscity);
            News news = new News(gpscity);

            gpscity.visit(new City("Mar del Plata"));

            gpscity.visit(new City("Tandil"));

        }
    }

Como podemos apreciar, cada vez que se ejecuta el método visit(), se dispara el evento update() para que las diferentes entidades actualicen sus estados con la nueva ciudad ingresada. Veamos la salida en pantalla el resultado correspondiente.

/