lunes, 9 de enero de 2012

Patrones de diseño: Observer

Otro de los patrones de diseño más utilizados es el llamado Observer, que se enmarca dentro de los patrones de comportamiento. Este patrón representa la situación en la que tenemos uno o varios objetos (observers) pendientes del estado de otro (subject). Los observers se suscriben al objeto que quieren monitorear y este, cuando se producen cambios significativos en su estado, se lo notificará.
En .NET para implementar este patrón de diseño se me ocurren dos posibilidades. En primer lugar, podemos guardar en el objeto a monitorear una lista de los objetos observer y cuando haya que notificar algún cambio se recorre la lista de suscriptores y se ejecuta el método apropiado en cada elemento de dicha lista. Otro modo de realizar la tarea es utilizando delegados.
A continuación, muestro el código de un ejemplo muy intuitivo realizado de los dos modos comentados anteriormente. La idea es implementar una emisora de radio, y retransmitir para todos los oyentes que la tienen sintonizada.
El código que se muestra a continuación nos permite ver el resultado de implementar la primera versión.

using System;
using System.Collections.Generic;

namespace ObserverEmisoraRadio01
{
    class Program
    {
        static void Main(string[] args)
        {
            //Probando el patrón de diseño Observer
            IOyente oyente1 = new Oyente("Antonio");
            IOyente oyente2 = new Oyente("Marta");
            IOyente oyente3 = new Oyente("Raquel");
            //Creamos una emisora de radio y asignamos una canción inicial sonando
            eFM emisoraFM = new eFM("Ninguna");

            //Suscribimos los oyentes que sintonizan la emisora
            emisoraFM.Sintonizar(oyente1);
            emisoraFM.Sintonizar(oyente2);
            emisoraFM.Sintonizar(oyente3);

            //Cambiamos la canción que suena (debería notificarse a los oyentes)
            emisoraFM.CancionSonando = "On the floor";

            Console.WriteLine();

            //Cambiamos la canción que suena (debería notificarse a los oyentes)
            emisoraFM.CancionSonando = "Tonight";

            Console.WriteLine();

            //Desintonizamos uno de los oyentes
            emisoraFM.Desintonizar(oyente2);

            //Cambiamos la canción que suena (debería notificarse sólo a los oyentes todavía sintonizados)
            emisoraFM.CancionSonando = "Eye of the tiger";

            Console.ReadLine();
        }
    }


    //....
    //Clase abstracta que representa una emisora de radio genérica
    abstract class EmisoraRadio
    {
        private string _cancionSonando;
        private List<IOyente> _oyentes = new List<IOyente>();

        //Constructor
        public EmisoraRadio(string cancion)
        {
            _cancionSonando = cancion;
        }

        public string CancionSonando
        {
            get
            {
                return _cancionSonando;
            }
            set
            {
                if (_cancionSonando.CompareTo(value) != 0)
                {
                    _cancionSonando = value;
                    Notificar();
                }
            }

        }

        //
        //Método que envía una notificación a todos los oyentes que tienen la emisora
        //sintonizada
        public void Notificar()
        {
            foreach (IOyente oyente in _oyentes)
            {
                oyente.Update(this);
            }
        }

        //
        //Método que permite a un oyente suscribirse a una emisora
        public void Sintonizar(IOyente oyente)
        {
            _oyentes.Add(oyente);
        }

        //
        //Método que permite a un oyente desvincularse de una emisora
        public void Desintonizar(IOyente oyente)
        {
            _oyentes.Remove(oyente);
        }
    }

    //....
    //Clase que implementa una emisora de radio concreta
    class eFM : EmisoraRadio
    {
        public eFM(string cancion) : base(cancion) { }
    }

    //....
    //Interfaz que deben implementar los oyentes para poder escuchar alguna emisora de radio
    interface IOyente
    {
        void Update(EmisoraRadio emisora);
    }

    //....
    //Clase que se debe instanciar para crear los oyentes
    class Oyente:IOyente {
        private string _nombre;
        private EmisoraRadio _emisora;

        public Oyente(string nombre)
        {
            this._nombre = nombre;
        }

        public EmisoraRadio Emisora
        {
            get { return _emisora; }
            set { _emisora = value; }            
        }

        public void Update(EmisoraRadio emisora)
        {
            Console.WriteLine("Oyente " + _nombre + " escucha canción " + emisora.CancionSonando);
        }
    }
}
El resultado de ejecutar el código anterior se puede ver en la imagen siguiente. Dicho resultado coincide perfectamente con el esperado.

Por último, podemos ver el código correspondiente a la segunda versión planteada (utilizando delegados). La estructura básica del programa es semejante al caso anterior, aunque hay que hacer algunos cambios en algunos métodos. El código está comentado para facilitar la comprensión del mismo. El resultado de la ejecución será igual al caso anterior.

using System;
using System.Collections.Generic;

namespace ObserverEmisoraRadio01
{
    class Program
    {
        static void Main(string[] args)
        {
            //Probando el patrón de diseño Observer

            //Creamos los oyentes
            IOyente oyente1 = new Oyente("Antonio");
            IOyente oyente2 = new Oyente("Marta");
            IOyente oyente3 = new Oyente("Raquel");

            //Creamos una emisora de radio y asignamos una canción inicial sonando
            eFM emisoraFM = new eFM("Ninguna");

            //Suscribimos los oyentes que sintonizan la emisora
            oyente1.Sintonizar(emisoraFM);
            oyente2.Sintonizar(emisoraFM);
            oyente3.Sintonizar(emisoraFM);

            //Cambiamos la canción que suena (los oyentes que tengan la emisora sintonizada serán informados del cambio de canción)
            emisoraFM.CancionSonando = "On the floor";

            Console.WriteLine();

            //Cambiamos la canción que suena (los oyentes serán informados del cambio de canción)
            emisoraFM.CancionSonando = "Tonight";

            Console.WriteLine();

            //Desintonizamos uno de los oyentes (no será notificado de los cambios de canción a partir de aquí)
            oyente2.Desintonizar(emisoraFM);

            //Cambiamos la canción que suena (debería notificarse sólo a los oyentes todavía sintonizados)
            emisoraFM.CancionSonando = "Eye of the tiger";

            Console.ReadLine();
        }
    }


    //....
    //Clase abstracta que representa una emisora de radio genérica
    abstract class EmisoraRadio
    {
        private string _cancionSonando;
        //Definimos e instanciamos el delegado
        public delegate void SongChangeHandler(string tituloCancion);
        public SongChangeHandler OnSongChange;

        //Constructor
        public EmisoraRadio(string cancion)
        {
            _cancionSonando = cancion;
        }

        public string CancionSonando
        {
            get
            {
                return _cancionSonando;
            }
            set
            {
                if (_cancionSonando.CompareTo(value) != 0)
                {
                    _cancionSonando = value;
                    Notificar(value);
                }
            }

        }

        //
        //Método que envía una notificación a todos los oyentes que tienen la emisora
        //sintonizada
        public void Notificar(string cancion)
        {
            if (OnSongChange != null)
            {
                //Invoca el delegado: notifica todos los objetos agregados. La función que se ejecutará recibe como parámetro la cadena cancion.
                OnSongChange(cancion);
            }
        }

    }

    //....
    //Clase que implementa una emisora de radio concreta
    class eFM : EmisoraRadio
    {
        public eFM(string cancion) : base(cancion) { }
    }

    //....
    //Interfaz que deben implementar los oyentes para poder escuchar alguna emisora de radio
    interface IOyente
    {
        void Sintonizar(EmisoraRadio emisora);
        void Desintonizar(EmisoraRadio emisora);
    }

    //....
    //Clase que se debe instanciar para crear los oyentes
    class Oyente : IOyente
    {
        private string _nombre;
        private EmisoraRadio _emisora;
        private EmisoraRadio.SongChangeHandler songChanged;

        public Oyente(string nombre)
        {
            this._nombre = nombre;
            songChanged = null;
        }

        public EmisoraRadio Emisora
        {
            get { return _emisora; }
            set { _emisora = value; }
        }


        public void Sintonizar(EmisoraRadio emisora)
        {
            songChanged = new EmisoraRadio.SongChangeHandler(this.CambioCancion);
            emisora.OnSongChange += songChanged;
        }

        public void Desintonizar(EmisoraRadio emisora)
        {
            if (songChanged != null)
            {
                emisora.OnSongChange -= songChanged;
            }
        }

        public void CambioCancion(string tituloCancion)
        {
            Console.WriteLine("Oyente " + _nombre + " escucha canción " + tituloCancion);
        }
    }
}

No hay comentarios:

Publicar un comentario