Mixins en C# – Un ejemplo: Memento

Después de la pequeña introducción a los mixins, veamos un ejemplo de uso. La verdad es que todos los ejemplos que se me ocurren son bastante malos, pero al menos servirán para ver mejor la idea de mixin.

OJO: Este ejemplo es sólo eso: un ejemplo. Ni siquiera tengo muy claro que los mixins sean la forma más apropiada de implementarlo, pero como ejemplo me sirve.

Memento

Supongamos (y es mucho suponer), que queremos implementar por algún extraño motivo el patrón memento sobre una serie de clases que ya tenemos implementadas. La idea básica de este patrón es que podemos generar un memento de un objeto concreto para encapsular el estado que tiene el objeto en ese instante. Posteriormente, con ese memento es posible pedirle al objeto que lo originó que vuelva a ese estado.

Para implementar el patrón, tendremos un interface IMementoOriginator que deberán implementar las clases que soporten esta funcionalidad:

public interface IMementoOriginator
{
    IMemento CreateMemento();
    void Restore(IMemento memento);
}

public class Person // <- Debería implementar IMementoOriginator
{
    public string Name { get; set; }
}

var person = new Person { Name = "Belén Esteban" };
var memento = person.CreateMemento();

person.Name = "Julián Muñoz";
Debug.Assert(person.Name == "Julián Muñoz");

person.Restore(memento);
Debug.Assert(person.Name == "Belén Esteban");

Dejando de lado la utilidad del patrón memento, ¿cómo pueden ayudarnos los mixins a implementar esto? Si no tenemos accesso a la clase Person y no podemos modificarla para que implemente el interface IMementoOriginator, podemos crear un mixin que dote de esa funcionalidad a la clase.

[Extends(typeof(Person)]
public class PersonMementoOriginator : Mixin<Person>, IMementoOriginator
{
    private class PersonMemento : IMemento
    {
        public string PersonName { get; set; }
    }

    public IMemento CreateMemento()
    {
        return new PersonMemento { PersonName = Target.Name; }
    }

    public void Restore(IMemento memento)
    {
        if (!(memento is PersonMemento))
            throw new ArgumentException("memento");

         var personMemento = (PersonMemento)memento;
         Target.Name = personMemento.Name;
    }
}

Este ejemplo esta realizado con la estupenda librería re-mix. Vamos a ver paso a paso que estamos haciendo:

Lo primero que hacemos es indicar que queremos crear un mixin que será aplicado a la clase Person. Para ello usamos el atributo [Extends] e indicamos la clase que será ampliada.

La clase con la que implementamos el mixin no tiene ningún requisito concreto, pero en este caso, como queremos poder acceder al objeto que estamos aumentando, resulta cómodo heredar de la clase Mixin<TTarget>. Esto nos permite acceder luego a la propiedad TTarget Target que contendrá una referencia al objeto original (en este caso, un objeto Person).

A continuación, necesitamos implementar la funcionalidad que queremos cubrir con este mixin, en nuestro caso la creación de un memento y la recuperación desde el mismo. Aquí es donde aprovechamos la propiedad Target para poder acceder al Person que estamos aumentando.

Para poder crear objetos con los mixins que tienen asociados, re-mix ofrece una factoría que se encarga de hacer la magia necesaria para que todo esto funcione:

var person = ObjectFactory.Create<Person>();
person.Name = "Maruja Díaz";

var memento = ((IMementoOriginator)person).CreateMemento();
person.Name = "Víctor Sandoval";
Debug.Assert(person.Name == "Víctor Sandoval");

((IMementoOriginator)person).Recover(memento);
Debug.Assert(person.Name == "Maruja Díaz");

La única cosa un poco (vale, muy) fea es el cast a IMementoOriginator, pero C#3 es un lenguaje de tipado estático y no hay muchas opciones. En el momento de compilar, Person no tiene ningún método de IMementoOriginator, por lo que la única forma de compilar el código es con el cast. Utilizando C#4, se podría recurrir al uso de dynamic y evitar así el cast.


Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *

*

Puedes usar las siguientes etiquetas y atributos HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>