martes, 26 de febrero de 2013

Patrón Observer en C# para Chilean people

Hola a todos mis queridos computines ñoños, esta vez les explicaré pasito a pasito como implementar el Patrón de Diseño Observer, de forma sencilla  pero explicado al estilo chilensis para entendamos todos los que nos aburre aquellos lenguajes y tecnicismos a veces exagerado ;)

¿Que es el patrón de diseño?

En palabras simples es una forma o estilo solución para un problema dado o conocido en la creación de software. Estos patrones son soluciones probadas por lo que en vez de ponerse uno a solucionar un problema desde 0, puede usar uno de estos patrones ya creados.

Existe el libro maestro, un tanto antiguo  pero de todas formas es considerado un biblia, el libro es conocido como GoF o Gang-Of-Four ("La banda de los Cuatro" seguramente por que lo escribieron 4 autores). Se puede comprar acá por casi 40 dólares, está en ingles:
http://www.amazon.com/Design-Patterns-Object-Oriented-Addisonwesley-Professional/dp/0201633612
o bajarlo de http://www.uml.org.cn/c++/pdf/DesignPatterns.pdf

¿Que es el patrón de diseño Observer?

Es un patrón clasificado dentro del grupo de comportamiento (hay 3 tipos de patrones) que permite que un objeto X le avise a N subscriptores de algún cambio de estado. De esta forma los subscriptores no tienen que preguntar eternamente por algún cambio sino que el objeto X, ahora llamado Sujeto, le informa su cambio a los subscriptores, ahora llamados Observadores. Además de realizarse una sincronización de todos los observadores con ese valor.

Si separamos un poco la estructura tenemos estos 5 elementos:
  • Un Sujeto: es una clase abstracta (por lo que otra clase heredará de ella y la utilizará), permite básicamente 3 cosas, registrar o subscribir un observador, de-suscribirlo, notificar a los observadores de algún cambio. 
  • Un Sujeto real o Sujeto concreto: Es objeto real con el cual se trabajará desde otras clases, por lo que hereda de Sujeto y utiliza sus métodos.
  • Un Observador: Es una interfaz (en el código será IObservador) que contiene las acciones que puede realizar un Observador real. Por ahora, solo tiene una acción: Actualizar(valor) por pantalla. Ojo que al ser una interfaz, no implementa esta acción, solo las declara.
  • N Observadores reales o concretos: Estas son objetos que representan tus clases propias, como Cliente, Producto, etc. Esta clase implementa la acción Actualizar(valor) de la interfaz Observador. Puse N ya que es sólo 1 una clase en verdad, pero puedes tener N instancias de ella en el programa principal que hará el llamado a crear estas instancias.
  • Programa o main: a manera de ejemplo la tenemos, en su caso puede que tengan una clase de negocio o algo más complejo.
Algunos novatillos se preguntarán por qué esta estructura, o porque 5 objetos y no 4 o 2. Resulta que los patrones se basan mucho en una estructura de herencia de clases por lo tanto se busca especializar mucho los objetos. Por ejemplo si el objeto es genérico, se declara como Abstracto. Si queremos definir su comportamiento pero no implementarlo inmediatamente usamos Interfaces. No podemos usar una misma clase para todo ya que no estamos frente a una programación estructurada sino a una programación orientada a objetos.

He comparado el modelo de clases de diferentes artículos o libros y la base es la misma, pero varia un poco entre cada una de ellas. Nosotros haremos nuestra propia versión que tiene la base del modelo indicado en el libro "Gang of Four":

Modelo de clases según Wikipedia en inglés.

Modelo de clases según Wikipedia en español.

Modelo de clases según libro "Gang of Four".

Nuestro modelo que implementaremos, vista Diagrama de clases de Visual Studio 2012.

Dejando la cháchara, vamos a meter los dedos.

Metiendo los deditos

El 1er. objeto crear es Observador, que es una interfaz que le llamaremos IObservador:

using System;

namespace ConsoleApplication1
{
    // Observador es una interfaz que define que método que el ObservadorConcreto debe implementar
    interface IObservador
    {
        void Actualiza(string valor);
    }
}

El 2do. elemento es la clase Observador concreto o real que implementará el método Actualiza de la interfaz. Define una variable valor que es compartido por el resto de los otros Observadores concretos y que debe sincronizarse en caso de un cambio por parte del Sujeto con tal de que todos los Observadores tengas el mismo valor:

using System;

namespace ConsoleApplication1
{
    // Esta clase es tu clase "producto", "cliente" o como le llames, yo le 
    // puse así ya que conceptualmente es un Observador real que implementa 
    // el método definido en la interfaz. 
    // Tiene la propiedad compartida por el resto de los observadores (en este 
    // caso una propiedad llamada Valor) y que es la que los otros 
    // observadores quieren 
    // monitorear, también actualiza su valor e imprime por pantalla el 
    // cambio si el Sujeto le dice que lo haga.
    class ObservadorContreto: IObservador
    {
        string nombre;
        string valor = "0"; //por defecto

        //Constructor: definimos un nombre para el observador real
        public ObservadorContreto(string nombre)
        {
            this.nombre = nombre;
        }

        // Notifica y actualiza el nuevo valor en este ObservadorConcreto 
        // e imprime en pantalla.
        public void Actualiza(string valor)
        {
            // Actualizamos el valor con tal de sincronizar
            this.valor = valor;
 
            Console.WriteLine("Se notifica a {0} que Sujeto ha cambiado 
            el Valor a {1}", nombre, valor);
        }
    }
}

El 3er. elemento es la clase Sujeto, que es abstracta, y usa métodos del Observador concreto creado recientemente:
using System;
using System.Collections;

namespace ConsoleApplication1
{
    // Clase abstracta que almacena una colección de observadores subscritos 
    // y los notifica de algún cambio, le dice a cada ObservadorConcreto 
    // subscrito que actualize el valor e imprima en pantalla el cambio.
    // Es responsabilidad de cada ObservadorConcreto imprimir en pantalla, 
    // pero es responsabilidad del Sujeto indicarles a ellos que lo hagan.
    abstract class Sujeto
    {
        ArrayList coleccionObservadores = new ArrayList();
        
        public void RegistraObservador(ObservadorContreto observador)
        {
            coleccionObservadores.Add(observador);
        }

        public void DesRegistraObservador(ObservadorContreto observador)
        {
            coleccionObservadores.Remove(observador);
        }

        public void NotificaAObservadores(string valor)
        {
            foreach (ObservadorContreto observadores in coleccionObservadores)
            {
                observadores.Actualiza(valor);
            }
        }      
    }
}

El 4to. elemento es la clase Sujeto concreto o real, que hereda de Sujeto:
using System;

namespace ConsoleApplication1
{
    // Clase real que implementa el Sujeto. Esta clase es usada para 
    // indicar que cualquier cambio afecta a las otros observadores
    class SujetoContreto: Sujeto
    {
        public void CambiaValor(string valor)
        {
            NotificaAObservadores(valor);
        }
    }
}

El 5to. y último elemento es la clase Programa o Main, que utiliza la clase SujetoConcreto para registrar Observadores y cambiar un valor que se replicará o sincronizará en cada uno de ellos y se notificará en pantalla. Luego de des-registran 2 Observadores y cambia de nuevo el valor para ver a quien informa:
using System;

namespace ConsoleApplication1
{
    class Programa
    {
        static void Main(string[] args)
        {
            SujetoContreto sujetoConcreto = new SujetoContreto();

            // Creamos 4 observadores concretos o "sapos"
            ObservadorContreto observdor1 = new ObservadorContreto("Sapo1");
            ObservadorContreto observdor2 = new ObservadorContreto("Sapo2");
            ObservadorContreto observdor3 = new ObservadorContreto("Sapo3");
            ObservadorContreto observdor4 = new ObservadorContreto("Sapo4");

            // SujetoConcreto le dice a Sujeto que agrege un nuevo 
            // ObservadorConcreto a su colección. Esto lo hace el Sujeto 
            // sin derivarle esta tarea a nadie más
            sujetoConcreto.RegistraObservador(observdor1);
            sujetoConcreto.RegistraObservador(observdor2);
            sujetoConcreto.RegistraObservador(observdor3);
            sujetoConcreto.RegistraObservador(observdor4);

            // Por medio de SujetoConcreto decimos que cambiará el valor, 
            // pero lo que por abajo pasa es que el SujetoConcreto 
            // le dice al Sujeto 
            // que actualice el valor, y el Sujeto lo 
            // que hace es decirle a cada ObservadorConcreto que lo haga
            sujetoConcreto.CambiaValor("10");            

            Console.WriteLine("");

            // Sacamos a 2 ObservadoresConcretos, dejando solo a 2. 
            // Esto lo hace el Sujeto en verdad sin darle la tarea a nadie más
            sujetoConcreto.DesRegistraObservador(observdor3);
            sujetoConcreto.DesRegistraObservador(observdor4);

            // Hacemos otro cambio de valor
            sujetoConcreto.CambiaValor("20");

            Console.Read();
        }
    }
}

Salida

La salida del test  nos da:

Se notifica a Sapo1 que Sujeto ha cambiado que el Valor a 10
Se notifica a Sapo2 que Sujeto ha cambiado que el Valor a 10
Se notifica a Sapo3 que Sujeto ha cambiado que el Valor a 10
Se notifica a Sapo4 que Sujeto ha cambiado que el Valor a 10

Se notifica a Sapo1 que Sujeto ha cambiado que el Valor a 20
Se notifica a Sapo2 que Sujeto ha cambiado que el Valor a 20

Descarga de ejemplo

3 comentarios: