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);
        }
    }
}

lunes, 2 de enero de 2012

Patrones de diseño: Singleton

Estoy intentando ampliar mis conocimientos acerca de los patrones de diseño. Es un tema muy interesante, y además ayuda a profundizar y mejorar la comprensión sobre ciertos conceptos avanzados del diseño orientado a objetos.
He decidido empezar con el patrón llamado Singleton por su sencillez y porque es uno de los más conozco y he utilizado en ocasiones. Este patrón permite garantizar que no se pueda instanciar más de un objeto de una determinada clase. A continuación se puede ver el código de una clase que implementa el patrón Singleton de modo seguro en entornos multitarea mediante el uso de bloqueos.

    public sealed class Singleton
    {
        static Singleton instance = null;
        static readonly object padlock = new object(); //Objeto compartido para bloquear el acceso simultáneo

        public static String datos;
        Singleton()
        {
        }

        public static Singleton getInstance()
        {
            lock (padlock)
            {
                if (instance == null)
                {
                    instance = new Singleton();
                    datos = "Dato inicial";
                }
                return instance;
            }
        }
    }
Podemos ver con un ejemplo como se comporta la clase Singleton cuando se invoca varias veces. Se observa que en la variable datos de la clase se tiene el dato introducido inicialmente, y en ningún caso varía cuando se intenta obtener una nueva instancia de la clase porque la instancia obtenida es siempre la misma.

using System;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Singleton.getInstance();
            Console.WriteLine("Dato inicial: " + Singleton.datos);
            Singleton.datos = "Dato cualquiera";

            for (int i = 1; i <= 50; i++)
            {
                Thread t1 = new Thread(ThreadMain);
                t1.Name = "Hilo_" + i.ToString();
                t1.Start();
            }

            Console.ReadLine();
        }

        static void ThreadMain()
        {
            //
            Singleton.getInstance();
            Console.WriteLine(Thread.CurrentThread.Name + "  " + "datos: " + Singleton.datos);
        }
    }

A continuación se puede ver el resultado de la ejecución del código de prueba.


Versión 2:

public sealed class MySingleton
{
    private static MySingleTon _instance;

    private MySingleton()
    {
    }

    public static MySingleton Instance()
    {
        if(_instance == null)
        {
            _instance = new MySingleton();
        }
        return _instance;
    }
}

static void Main(string[] args)
{
    MySingleton single1 = MySingleton.Instance();
    MySingleton single2 = MySingleton.Instance();

    if(single1 == single2)
    {
        Console.WriteLine("These two objects are the same.");
    }
}