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!