Patrón Observador en C# con Delegates y Eventos

En C++, el patrón Observador (también conocido como Publisher/Subscriber) es usualmente implementado como se sugiere en la biblia de los patrones de diseño:

Gang of Four

Dicha implementación requiere que el observador y el observado implementen sendas interfaces, de modo que cuando el observado cambie, notifique a todos sus observadores.

De más está decir que esto puede hacerse de la misma forma en C#, considerando que C# no permite herencia múltiple, pero sí la implementación de más de una interfaz. Sin embargo, puede decirse que en C# el patrón Observador viene implementado en el propio lenguaje, a través de los mecanismos de delegates y eventos.

Supongamos el siguient caso genérico: tenemos una clase del modelo que es observada por una clase de la vista. El patrón MVC nos dice que la vista puede tener una referencia al modelo, pero la dependencia inversa es síntoma de mal diseño. Es ahí donde el patrón observador viene al rescate. Una posible solución con delegates y eventos es la siguiente:

En primer lugar, en el modelo debemos declarar el evento asociado a cambios en el modelo, y el delegate que define el encabezado de sus handlers. Además, en cada método donde querramos notificar a la vista, debemos disparar el evento.

namespace App.Modelo{
    public class Observado{
        // Atributos de estado
        // ...
        // Atributos para el patron observador
        // El delegate que los observadores deben implementar
        public delegate void CambioEnModeloHandler(object modelo, EventArgs e);
        // El evento al que los observadores deben suscribirse
        public event CambioEnModeloHandler cambioEnModelo;
        // Métodos
        // ...
        //Método que dispara el evento de cambio de estado
        protected void OnCambioEnModelo(object modelo, EventArgs e){
            //Si hay observadores, notificarles lanzando el evento
            if (cambioEnModelo != null){
                cambioEnModelo(this, e);
            }
        }
        //Un método cualquiera de esta clase que genera cambios a notificar
        public void f(){
           //Luego de hacer cosas que cambien el estado del modelo...
           OnCambioEnModelo(this, e); // Llenar e con la info pertinente o pasar null
        }
    }

    //Si queremos pasar info extra a la vista, podemos hacer esto.
    //Si no, podemos pasar null como segundo argumento
    public class CambioEnModeloEventArgs : EventArgs{
        //Atributos con info a pasar a la vista
        //Métodos para actualizar/computar esa info
    }
}

Como segundo y último paso, en la vista debemos implementar un método con el mismo encabezado que el delegate, y suscribirnos al evento de cambios en el modelo declarando dicho método como el handler:

namespace App.Vista{
    public partial class Vista : Window{
        // Atributos
        // ...
        // Referencia al modelo
        public Modelo.Observado modelo { get; set; }
        // Métodos
        public Vista(){
            this.InitializeComponent();
            // Creo el modelo y me suscribo al evento de cambios en él
            modelo = new Modelo.Buffer(tamBuffer);
            modelo.cambioEnModelo += new Modelo.Observado.CambioEnModeloHandler(CambioElModelo);
            // Inicializar otras cosas...
        }
        // El handler para el evento de cambios en el modelo
        public void CambioElModelo(object modelo, EventArgs e){
            if (this.Dispatcher.CheckAccess()){
                //Actuar en base a los cambios
            } else {
                // Si este thread no puede acceder a la GUI; lo pongo en su dispatcher
                this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                        (Action)(() =>
                        {
                            //Actuar en base a los cambios
                        }));
            }
        }
}

Sobre la última parte, se verifica si el thread puede acceder a la GUI, porque en WPF, por defecto, sólo el thread de la ventana puede alterarla. El método BeginInvoke nos permite evitar esa restricción.

¡IMPORTANTE! Para cada +=, debemos hacer un -= al terminar de usar el evento (o si ya no queremos manejarlo). Si no lo hacemos, quedan referencias a los handlers y esto es una fuente muy común de leaks en .NET.

Comments

Popular posts from this blog

VB.NET: Raise base class events from a derived class

Apache Kafka - I - High level architecture and concepts

Upgrading Lodash from 3.x to 4.x