En este artículo explicaré los patrones de diseño Fluent Interface y Builder y cómo usarlos juntos para hacer que nuestro código sea más limpio y legible.

Crearemos un proyecto de ejemplo usando los dos patrones. Puedes seguir el artículo paso a paso, clonar el proyecto desde mi repositorio público en BitBucket si usas Git, o simplemente descargar el archivo ZIP usando este enlace, y jugar un poco con él si eres más autodidacta.

El patrón Fluent Interface

El patrón Fluent Interface se usa para hacer el código más legible y fácil de entender. Está basado en usar el mismo contexto pasado como valor de retorno de manera que pueda ser usado de nuevo en la próxima acción, generando una sentencia con métodos en cascada, o encadenación de métodos.

El contexto general de un interfaz fluent debería ser:

  • Definido por el valor de retorno del método llamado.
  • Auto-referenciado, donde el nuevo contexto es equivalente al último contexto.
  • Terminado mediante el retorno de un contexto vacío (hay variantes sobre esto que luego veremos).

El patrón Builder

El patrón builder nos permite separar un objeto del código que lo construye. Se usa para construir objetos complejos o grupos de éstos.

Usando este patrón evitamos la creación de múltiples constructores para la clase, cosa que nos llevaría al anti-patrón telescoping constructor (tener montones de constructores diferentes para un solo objeto complejo) que hace el código mucho más difícil de leer y comprender.

Para aplicar éste patrón normalmente necesitaremos la clase del objeto en sí mismo, una clase builder, y eventualmente, en caso de tener múltiples objetos complejos relacionados, una clase director.

Mezclémoslo!

Mezclando los dos patrones, podemos conseguir un código elegante y legible al crear nuestros objetos, que podría ser algo parecido a ésto:

Square mySquare = new PolyShapeBuilder<Square>()
            .SetX(100)
            .SetY(100)
            .SetColor(Color.Blue)
            .SetFillColor("#FFFF00")
            .AddVector(new Vector(90, 100))
            .AddVector(new Vector(90, 100))
            .AddVector(new Vector(90, 100))
            .Build();

Chulo, eh? Ok, pues vamos a por ello!

Haciéndolo

Lo primero que vamos a necesitar es un interfaz para nuestra forma poligonal, que llamaremos IPolyShape:

public interface IPolyShape
{
    int X { get; set; }
    int Y { get; set; }
    Color Color { get; set; }
    Color FillColor { get; set; }
    bool Closed { get; set; }
    List<Vector> Vectors { get; set; }
}

Ahora comenzamos con nuestra clase builder. Haremos una clase genérica de manera que podamos crear o pasarle cualquier objeto que implemente IPolyShape sin problemas:

public class PolyShapeBuilder<T> where T : IPolyShape, new()
{
    private T _shape;

    public PolyShapeBuilder(T shape)
    {
        _shape = shape;
    }

    public PolyShapeBuilder() : this(new T()) { }
}

Vamos por partes… nuestro builder tiene un campo privado de tipo T (genérico). Éste es el campo que contiene el objeto que vamos a “decorar” o cambiar usando los métodos de la clase, y que será el resultado cuando acabemos la tarea de construcción.

Tambíen hemos creado dos constructores. El primero toma un objeto ya creado para poder cambiar sus propiedades. El segundo es el constructor sin parámetros por defecto, que simplemente crea un nuevo objeto de la clase especificada y lo pasa al constructor anterior.

Ahora añadiremos los métodos decoradores, que irán moldeando el objeto resultante. Éstos métodos cambian las propiedades del objeto interno y devuelven el mismo builder como contexto, siguiendo el patrón Fluent Interface de manera que podamos encadenar una nueva llamada:

public PolyShapeBuilder<T> SetY(int y)
{
    _shape.Y = y;
    return this;
}

Hacer esto con todos los métodos decoradores nos permite utilizar el resultado del método como inicio de la siguiente llamada, encadenando las llamadas al builder.

Todos los builders, como su nombre indica, deben finalizar construyendo nuestro objeto, y todos los interfaces fluent deberían finalizar en una llamada vacía rompiendo la cadena de métodos. La mejor manera de mezclar aquí los dos patrones entonces, es hacer que nuestro Build() devuelva el objeto final, acabando con la cadena del builder de la siguiente manera:

public T Build()
{
    return _shape;
}

La clase Square

Para poder realizar la prueba, crearemos una clase Square que implemente IPolyShape:

public class Square : IPolyShape
{
    public int X { get; set; }
    public int Y { get; set; }
    public Color Color { get; set; }
    public Color FillColor { get; set; }
    public bool Closed { get; set; }
    public List<Vector> Vectors { get; set; }

    public Square()
    {
        Closed = true;
        Vectors = new List();
    }
}

Finalizando la aplicación y ejecutando

Ahora creamos la clase principal de la aplicación de cónsola así:

static void Main(string[] args)
{
    Square mySquare = new PolyShapeBuilder<Square>()
        .SetX(100)
        .SetY(100)
        .SetColor(Color.Blue)
        .SetFillColor("#FFFF00")
        .AddVector(new Vector(90, 100))
        .AddVector(new Vector(90, 100))
        .AddVector(new Vector(90, 100))
        .AddVector(new Vector(90, 100))
        .Build();

    Console.WriteLine(
        string.Format("Tenemos un bonito rectángulo con {0} vectores, "+
                        "posicionado en {1},{2} de color {3} con fondo {4}!",
                    mySquare.Vectors.Count(), 
                    mySquare.X,
                    mySquare.Y,
                    mySquare.Color.Name, 
                    mySquare.FillColor.Name));

    Console.ReadLine();

}

Deberíamos obtener este resultado al ejecutar:
Tenemos un bonito rectángulo con 4 vectores, posicionado en 100,100 de color Blue con fondo ffffff00!

Gracias!

Eso es todo!!!

Espero que este pequeño artículo te ayude a crear código más limpio y comprensible. Yo me contento con eso.

No olvides de añadir las referencias a System.Drawing para la clase Color si la has usado en tu ejemplo, o simplemente descarga el proyecto completo en uno de estos enlaces:

Git en BitBucket
Archivo ZIP

Follow me

Joan Vilariño

Senior .NET Developer at Ohpen
Follow me