Crear Fluent Interfaces en C #

Eric Evans y Martin Fowler fueron los responsables de esta tendencia llamada Fluent Interface. El objetivo de la misma no era otro que el de mejorar la legibilidad del código y conseguir un uso más “fluido” de los métodos y propiedades relacionados con el objeto en cuestión. Hace unos meses fui consciente de este estilo de programación al ser aplicada a una serie de controles en ASP.NET MVC e incluso la disponibilidad de estas interfaces en algunas APIs de terceros. Para saber cómo son y de qué forma podríamos crear nuestras propias interfaces veamos un sencillo ejemplo:

Supongamos que tenemos una clase que construye ordenadores con sus respectivas propiedades:

class Machine
{
    public string Processor { get; set; }
    public int RAM { get; set; }
}

Si quisiéramos crear un nuevo equipo y además inicializar sus componentes de la forma convencional sería algo parecido a esto:

var newMachine = new Machine {RAM = 6, Processor = "Intel i7 920"};

Si además esta clase Machine tuviera una serie de métodos encargados de realizar acciones como encender el ordenador, testearlo, apagarlo, etcétera especificados en una interfaz llamada IMachine como en el siguiente trozo de código:

namespace FluentInterface
{
    interface IMachine
    {
        int RAM { get; set; }
        string Processor { get; set; }
        string Status { get; set; }

        void TurnOn();
        void TurnOff();
        void Test();
    }
}

 

using System;

namespace FluentInterface
{
    class Machine : IMachine
    {
        public string Processor { get; set; }
        public int RAM { get; set; }
        public string Status { get; set; }

        public void TurnOn()
        {
            Status = "On";

            Console.WriteLine("Your computer is {0}", Status);
        }

        public void TurnOff()
        {
            Status = "Off";

            Console.WriteLine("Your computer is {0}", Status);
        }

        public void Test()
        {
            Console.WriteLine("Your computer is {0}", Status);
        }

    }
}

ejecutaríamos cada uno de ellos invocando línea a línea los métodos declarados en dicha clase:

var newMachine = new Machine { RAM = 6, Processor = "Intel i7 920" };

newMachine.TurnOn();

newMachine.Test();

newMachine.TurnOff();

¿Qué nos ofrece Fluent Interface?

            new Computer()
                .AddProcessor("Intel i7 920")
                .AddRAM(6)
                .TurnOn()
                .Test()
                .TurnOff();

Si nos damos cuenta se trata de una línea de código abarcando todas las funciones que nos interesan de una clase de tipo Machine, encapsulada dentro de Computer. ¿Cómo creamos este encapsulamiento?

En primer lugar vamos a  definir una interfaz que establezca los métodos disponibles y obligatorios de implementar:

namespace FluentInterface
{
    interface IComputer
    {
        IComputer TurnOn();
        IComputer TurnOff();
        IComputer Test();
        IComputer AddRAM(int GB);
        IComputer AddProcessor(string model);
    }
}

Lo primero que nos puede llamar la atención es que cada uno de los métodos deberá retornar el tipo IComputer, es decir, debería devolver una clase que implemente esta interfaz que estamos declarando o dicho de otra forma: Deberá devolverse a sí mismo. ¿Qué conseguimos con ello? Devolver el contexto (valores de las variables) en cada acción que vayamos invocando para poder ser utilizado en la siguiente llamada. Como implementación válida a esta interfaz tendríamos lo siguiente:

using System;

namespace FluentInterface
{
    class Computer : IComputer
    {
        private readonly IMachine _machine;

        public Computer()
        {
            _machine = new Machine();
        }

        public IComputer TurnOn()
        {
            _machine.TurnOn();

            return this;
        }

        public IComputer TurnOff()
        {
            _machine.TurnOff();

            return this;
        }

        public IComputer Test()
        {
            _machine.Test();

            return this;
        }

        public IComputer AddRAM(int GB)
        {
            _machine.RAM = GB;

            Console.WriteLine("{0} GB RAM added. Do you think its enough?", _machine.RAM);

            return this;
        }

        public IComputer AddProcessor(string model)
        {
            _machine.Processor = model;

            Console.WriteLine("{0} processor added.", _machine.Processor);

            return this;
        }
    }
}

En cada uno de los métodos realizaremos las operaciones necesarias y finalmente retornaremos this, es decir, la instancia de la clase que implementa IComputer para poder continuar operando con ella de una manera fluida. Gracias a este estilo de programación conseguiremos un código más legible y atractivo para el desarrollador :)

¡Saludos!

10 comentarios

  • Excelente Blog, te felicito por tu dedicación y por la claridad de tus temas

  • Muy bueno tu post Gisela, bastante claro. Que bueno encontrar profesionales de nuestra lengua con buen nivel y que sepan dejarse entender, eso es muy importante.

    Saludos desde Lima.

    • Hola Oscar,
      Muchas veces comentarios como el tuyo son los que animan a seguir escribiendo y ayudando a los demás :)

      ¡Muchas gracias por tu comentario! Saludos desde España :D

  • Hola Gisela, primero que todo felicitarte por tan interesante material y dos, por la forma como lo das a conecer a la comunidad. Estare muy atento a toda la información que puedas publicar, ya que tu forma de impartir la información hace que toda mi atención quede cautivada.
    Agradezco nuevamente tus aportes y espero poder seguir siendo un lector consagrado a tu blog.

  • Hola Walter,
    Muchas gracias por tu comentario, me alegra que mi información te sea clara y de utilidad :)

    ¡Espero seguir leyendote por aquí! :D

    ¡Saludos!

  • Oye Gisela genial tu post ….. muy muy claro …… no habia podido entender lo que era las interfaces ……… pikos saludos desde Colombia.

  • Hola Gisela

    Quiero destacar en este punto que para que hoy podamos escribir nuestro código de forma fluída, hubo un equipo pionero alla por el año 1978 en el desarrollo del proyecto Smalltalk, ellos sentaron las bases de muchas cosas que hoy nos parecen novedosas o sacadas de una galera.

    Ya para esos entonces los metodos en Smalltalk devolvían por defecto ^self, o sea el mismo contexto, lo que hacía tener de manera predeterminada la forma de escribir código fluída.

    Sinceramente no soy un fanatico de Smalltalk, y en mi actulidad desarrollo con c#, aunque me doy cuenta cada día que c# y otros lenguajes modernos se le parecen cada vez más, por otro lado me alegra muchísimo, estoy seguro al final del camino todos los lenguajes van a tener mas cosas en común que diferencias…

    Quizas no aporte mucho mi comentario pero quiero destacar que nuestro hoy es en parte al duro trabajo de especialistas en Xerox hace ya mas de 3 décadas, lastima que para entonces SmallTalk fue dejado como jueguete científico y para un grupo muy reducido de programadores.

    Bueno agradezco cada uno de tus posts …
    sinceramente tu claridad para expresar los temas tratados es asombrosa.

    Exitos en tu carrera
    Saludos desde Argentina

    • Muchas gracias por tu comentario ggWily :) A veces es importante mirar hacia atrás y conocer el inicio de las cosas que, como bien dices, ¡Hoy nos parecen lo más!

      ¡Saludos desde España!

Deja un comentario