Patrones de diseño - Observador

El patrón Observador (Observer) esta englobado dentro de los patrones de comportamiento y permite definir un mecanismo de suscripción uno-muchos por el que los objetos que se suscriben reciben notificaciones cuando se produce un determinado evento.

Necesidad

Este patrón viene de la necesidad de tener que realizar acciones basadas en los cambios de otro objeto. Por ejemplo, podemos tener un interface de usuario con diferentes vistas de un valor y queremos que estas vistas se modifiquen de forma automática siempre que lo hace este valor, o lanzar eventos cuando se produzca determinado cambio en un fichero, se reciba un determinado correo, etc.

En este patrón intervienen principalmente, el sujeto que es observado y los observadores que reaccionaran a los eventos, con sus diferentes interfaces:

  • Sujeto: Es el interface que permite a los observadores suscribirse para recibir eventos
  • Observador: Define el interface de los objetos que serán notificados de los cambios en el sujeto
  • SujetoConcreto: Implementa el interface sujeto y envía las notificaciones a los ObservadoresConcretos que se subscriban
  • ObservadoresConcretos: Implementan el interface observador y responden a los eventos a los que se subscriben

Implementación

En este ejemplo vamos a suponer un sistema que esta monitorizando determinados archivos y queremos que se lancen determinados eventos de notificación (correo electrónico, mensajería XMPP, escritura en una DB) cuando se realiza algún cambio.

La representación de las clases sería así:

Patrón Observador

Código en Vala

Tenemos primero la clase abstracta  Observador que deben heredar los diferentes observadores. Esta tiene un atributo ManejadorEventos que utilizará cada observador para suscribirse y recibir notificaciones. También una clase abstracta update que sobrescribirán cada uno de los observadores implementando las acciones que llevarán a cabo cuando reciban la notificación:

Observador.vala
public abstract class Observador : GLib.Object 
{ 
    protected ManejadorEventos evento { get; set; } 
    public abstract void update (); 
} 

Ahora los diferentes observadores que heredan de la clase abstracta Observador. En el constructor reciben una instancia de ManejadorEventos que guardan y se suscriben como observadores utilizando el método addObservador.  Sobrescriben el método update con las funciones que realizarán cuando reciban la notificación, en este caso imprimir por pantalla el cambio de evento: 

ObservadorDB.vala
public class ObservadorDB : Observador 
{ 
    public ObservadorDB (ManejadorEventos evento) 
    { 
        this.evento = evento; 
        this.evento.addObservador (this); 
    } 
 
    public override void update () { 
        print ("Escribir en DB: %s\n", this.evento.estado); 
    } 
} 

ObservadorEmail.vala
public class ObservadorEmail : Observador 
{ 
    public ObservadorEmail (ManejadorEventos evento) 
    { 
        this.evento = evento; 
        this.evento.addObservador (this); 
    } 
 
    public override void update () { 
        print ("Escribir mail: %s\n", this.evento.estado); 
    } 
} 

ObservadorXMPP.vala
public class ObservadorXMPP : Observador 
{ 
    public ObservadorXMPP (ManejadorEventos evento) 
    { 
        this.evento = evento; 
        this.evento.addObservador (this); 
    } 
 
    public override void update () { 
        print ("Escribir mensaje XMPP: %s\n", this.evento.estado); 
    } 
} 

Lo siguiente es el SujetoConcreto, que en este caso llamamos ManejadorEventos. En este caso por simplicidad no utilizo una interface como indica la descripción, e implemento la clase directamente. Esta tiene como atributos una lista de observadores que incluirá a todos los que se suscriban a las notificaciones. También un atributo _estado que es en este caso el objeto que desencadena las notificaciones cuando cambia su estado. En su setter llamamos al método notificarObservadores que recorre la lista de observadores y llama a sus métodos update. También incluye el método addObservador para permitir la suscripción:

ManejadorEventos.vala
using Gee; 
 
public class ManejadorEventos : GLib.Object 
{ 
    private ArrayList<Observador> observadores = new ArrayList<Observador>(); 
     
    private string _estado = "normal"; 
     
    public string estado { 
        get { return _estado; } 
        set { _estado = value;  
            notificarObservadores (); 
        } 
    } 
 
    public void addObservador (Observador observador) 
    { 
        observadores.add (observador); 
    } 
 
    public void notificarObservadores () 
    { 
        foreach (var observador in observadores) { 
            observador.update (); 
        } 
    } 
} 

El cliente que utiliza el patrón lo tenemos en la clase UsoPatronObservador que instancia una clase ManejadorEventos y los tres observadores a los que se les incrusta ésta como parámetro en el constructor.  Cuando modificamos el estado en la instancia de ManejadorEventos se notificarán a los observadores que lanzan sus update correspondientes:

UsoPatronObservador.vala
class UsoPatronObservador : GLib.Object 
{ 
    public static int main (string[] args) 
    { 
        Intl.setlocale (ALL); 
 
        ManejadorEventos evento = new ManejadorEventos (); 
 
        new ObservadorDB (evento); 
        new ObservadorEmail (evento); 
        new ObservadorXMPP (evento); 
 
        evento.estado = "accediendo"; 
 
        return 0; 
    } 
} 

Construimos y ejecutamos el programa:

❯ valac --pkg=gee-0.8 ManejadorEventos.vala Observador.vala ObservadorEmail.vala ObservadorXMPP.vala ObservadorDB.vala UsoPatronObservador.vala -o UsoPatronObservador
❯ ./UsoPatronObservador
Escribir en DB: accediendo
Escribir mail: accediendo
Escribir mensaje XMPP: accediendo

Señal notify en Vala

En Vala, cada instancia de una clase que derivada de GLib.Object tiene una señal llamada notify. Esta señal se emite cada vez que cambia una propiedad del objeto. Para poder conectarse a esta señal si se está interesado en las notificaciones de cambios en general tendríamos:

obj.notify.connect((s, p) => { 
    stdout.printf("La propiedad '%s' ha cambiado!\n", p.name); 
}); 

s es la fuente de la señal (obj en este ejemplo), p es la información de propiedad de tipo ParamSpec para la propiedad modificada. Si solo estamos interesados en las notificaciones de cambio de una sola propiedad, por ejemplo la propiedad estado como en el ejemplo del patrón, podemos usar la sintaxis siguiente:

evento.notify["estado"].connect ((s, p) => { 
    obDB.update (); 
    obEmail.update (); 
    obXMPP.update (); 
}) 

En este caso cada vez que se produce una modificación en la propiedad estado se llaman a los métodos update de las diferentes clases. Tenemos que tener en cuenta que se debe utilizar la representación de cadena del nombre de la propiedad. En esta representación los guiones bajos se reemplazan por guiones: my_property_name se convierte en "my-property-name", que es la convención de nomenclatura de propiedades de GObject.

En algunos casos puede ser útil usar simplemente esta señal notify sin tener que recurrir al patrón observador.

Indice de Patrones

(0 votos)
Etiquetado como :

Deja un comentario

Asegúrese de introducir toda la información requerida, indicada por un asterisco (*). No se permite código HTML.