Gof – Builder

En esta ocasión vamos a hablar sobre el patrón de creación Builder, y su hace referencia a su propósito, la construcción de objetos complejos.

El propósito de este patrón es el siguiente:

Simplificar la construcción de objetos complejos definiendo para ello una clase principal encargada de construir instancias de otras.

Otra definición valida es:

Separa la construcción de un objeto complejo de su representación para que el mismo proceso de construcción puede crear diferentes representaciones.

Debemos aplicar este patrón cuando:

  • El algoritmo para la construcción del objeto debe ser independiente de las partes que lo conforman.
  • Se requiere más de un constructor en una clase o el mismo comienza a tener muchas líneas de código.
  • Se requiere lógica en la construcción del objeto.
  • El proceso de construcción debe permitir diferentes representaciones del objeto que es construido.
  • La estructura interna de los objetos a construir es compleja.
  • Si los atributos internos son dependientes entre si.
  • Si utilizamos objetos que son difíciles, o poco convenientes, de obtener durante la creación.

Modelo del patrón Builder:

Modelo del patrón Builder

Modelo del patrón Builder

Lo primero que podemos notar es que tenemos un Director que será el encargado de la construcción de los objetos. También podemos observar que una de las clases más importantes aquí es Builder, la cual posee definiciones de los pasos que se deben seguir para poder construir un objeto – producto – en particular. Finalmente tenemos los constructores concretos del producto a retornar.

Para hacerlo más práctico, vayamos trabajando en un ejemplo. En este caso la idea es trabajar en un construcctor (o preparador) de cockteles 🙂 . Lo primero que vamos a representar es el constructor de cockteles, el cual vamos a llamar CocktailBuilder.

public abstract class CocktailBuilder
{
    protected Bebida bebida;

    public Bebida Bebida
    {
        get { return bebida; }
    }

    public abstract void BuildBebidaPrincipal();    
    public abstract void BuildBebidaSecundaria();
    public abstract void BuildHielos();
    public abstract void BuildAperitivo();
}

Podemos notar que posee métodos abstractos para la construcción de cada uno de los distintos componentes de la bebida. Estos serán implementados por los builders propios de cada bebida. Además podemos ver el Producto representado por el atributo del tipo Bebida, el cual va a estar definido de la siguiente manera:

public class Bebida
{
    private string _nombreBebida;
    private string _tipoBebida;
    private Dictionary<string, string> _contenido = new Dictionary<string, string>();

    public Bebida(string nombre, string tipo)
    {
        this._nombreBebida = nombre;
        this._tipoBebida = tipo;
    }

    public string this[string key]
    {
        get { return _contenido[key]; }
        set { _contenido[key] = value; }
    }

    public void Show()
    {
        Console.WriteLine("\n---------------------------");
        Console.WriteLine("{0}:", _nombreBebida.ToUpper());
        Console.WriteLine("Tipo de bebida: {0}", _tipoBebida);
        Console.WriteLine(" Bebida principal : {0}", _contenido["principal"]);
        Console.WriteLine(" Bebida secundaria : {0}", _contenido["secundario"]);
        Console.WriteLine(" Hielo: {0}", _contenido["hielo"]);
        Console.WriteLine(" Aperitivo : {0}", _contenido["aperitivo"]);
    }
}

Ahora vayamos a los builders concretos para cada tipo de bebida, en este vamos a crear uno para un gran clásico de Argentina el Fernet y otro para el trago Bloody Mary:

public class FernetBuilder : CocktailBuilder
{
    public FernetBuilder()
    {
        bebida = new Bebida("Fernet", "Digestivo");
    }

    public override void BuildBebidaPrincipal()
    {
        bebida["principal"] = "Fernet Branca";
    }

    public override void BuildBebidaSecundaria()
    {
        bebida["secundario"] = "Coca Cola";
    }

    public override void BuildHielos()
    {
        bebida["hielo"] = "2 cubitos";
    }
    public override void BuildAperitivo()
    {
        bebida["aperitivo"] = "Ninguno";
    }
}
public class BloodyMaryBuilder : CocktailBuilder
{
    public BloodyMaryBuilder()
    {
        bebida = new Bebida("Bloody Mary", "Refrescante");
    }

    public override void BuildBebidaPrincipal()
    {
        bebida["principal"] = "Vodka";
    }

    public override void BuildBebidaSecundaria()
    {
        bebida["secundario"] = "Zumo de Tomate";
    }

    public override void BuildHielos()
    {
        bebida["hielo"] = "1 cubito";
    }

    public override void BuildAperitivo()
    {
        bebida["aperitivo"] = "1 pizca de sal y pimienta negra";
    }
}

Bien, solo nos falta representar al Director, en nuestro caso lo vamos a denominar Barman:

public class Barman
{        
    public void Construct(CocktailBuilder cocktailBuilder)
    {
        cocktailBuilder.BuildBebidaPrincipal();
        cocktailBuilder.BuildBebidaSecundaria();
        cocktailBuilder.BuildHielos();
        cocktailBuilder.BuildAperitivo();
    }
}

Como podemos ver, este constructor tiene definido una serie “compleja” de pasos a seguir para conseguir nuestro refrescante trago!

Finalmente vamos a escribir el programa principal y a instanciar cada una de esta entidades:

class Program
{
    static void Main(string[] args)
    {
        CocktailBuilder builder;
        Barman barman = new Barman();

        builder = new FernetBuilder();
        barman.Construct(builder);
        builder.Bebida.Show();

        builder = new BloodyMaryBuilder();
        barman.Construct(builder);
        builder.Bebida.Show();

        Console.ReadLine();
    }
}

En la salida podemos observar que cada uno de los tragos fue preparado de formas diferentes:

Aplicando builder

Aplicando builder

Adjunto el modelo de clases utilizado en el ejemplo para que puedan compararlo con el modelo de este patrón:

Modelo del ejemplo

Modelo del ejemplo

+ Ventajas

  • Facilita el manejo y supervisación en la creación de objetos complejos,  y permite implementar caminos alternativos ante errores.
  • Facilita el control en la creación de objetos que recurren a recursos externos (por ejemplo a conexiones de base de datos).

– Desventajas:

  • Gran acoplamineto entre el Director, los Builders y el Producto, lo que implica un alto impacto ante cambios.

GoF – Abstract Factory

En este primer post sobre los principios de diseños GoF vamos a hablar del patrón de creación Abstract Factory.

El propósito de este patrón es el siguiente:

Proporciona un contrato para crear familias de objetos relacionados sin tener que especificar un clase concreta.

Debemos aplicar este patrón cuando:

  • El cliente tiene que ser independiente del proceso de creación de los productos específicos.
  • Debemos convivir con más de una familia de productos dentro de la misma aplicación.
  • Necesitamos crear objetos como un conjunto y que sean compatibles entre ellos.
  • Deseamos proporcionar una colección de clases donde solo sea visible su contrato y no su implementación.

Modelo del patrón Abstract Factory:

Modelo Abstract Factory

Modelo Abstract Factory

En el modelo podemos identificar distintas familias de productos (en esta caso la 1 y la 2), y cada una de ellas compuestas por los productos A y B (esta más que claro que pueden existir n familias de objetos y n productos que las compongan). También podemos observar que existe una abstracción general para la fabricación de productos, la cual esta representada en el modelo como AbstractFactory, y sin importar de que familia de producto en concreto se trate, podremos a partir de la misma crear productos del tipo A o B (esto evita saber lo que realmente pasa por detrás).

Uno de los casos más representativos de este modelo en .NET es ADO.NET, en el cual nuestra Abstract Factory estaría representada por la clase DbProviderFactories y algunas de las familias de productos podrían ser SqlClient, OleDb o FireBirdClient entre otros. Entre los productos de estas familas podriamos encontrar objetos del tipo DbCommand, DbConnection DbException.

Pero mejor creemos nuestra propia abstract factory!… en nuestro ejemplo vamos a crear una factoría de encriptadores. Lo primero que vamos a definir son los productos que van a conformar la familia de objetos. Estos productos abstractos estarán representados por las clases Encriptador y Mensaje.

public abstract class Mensaje
{
    public Mensaje(string mensaje)
    {
        this.MensajeOriginal = mensaje;
    }

    public string MensajeOriginal { get; set; }
    public string MensajeEncriptado { get; set; }
    public Byte[] EncodedBytes { get; set; }
    public abstract void Mostrar();
}

public abstract class Encriptador
{
    public abstract void Encriptar(Mensaje m);
}

Básicamente la clase abstracta Encriptador representa un componente que puede encriptar texto y la clase abstracta Mensaje el contenido a encriptar. Ahora vamos a definir nuestra abstract factory de encriptadores la cual denominaremos CodificadorFactory. Dicha clase nos sirve como modelo para poder crear las distintas factories de encriptadores.

public abstract class CodificadorFactory
{
     public abstract Encriptador CrearEncriptador();

     public abstract Mensaje CrearMensaje(string mensaje);
}

Ahora a crear implementaciones “reales” de las factories de encriptadores!. En nuestro caso vamos a crear una factoría de encriptadores UTF8 y MD5.

public class UTF8Factory : CodificadorFactory
{
    public override Encriptador CrearEncriptador()
    {
       return new UTF8Encriptador();
    }

    public override Mensaje CrearMensaje(string mensaje)
    {
        return new UTF8Mensaje(mensaje);
    }
}

public class UTF8Encriptador : Encriptador
{
    private UTF8Encoding utf8 = new UTF8Encoding();

    public override void Encriptar(Mensaje m)
    {
        Byte[] encodedBytes = utf8.GetBytes(m.MensajeOriginal);

       foreach (Byte b in encodedBytes)
       {
            m.MensajeEncriptado += b;
        }

        m.EncodedBytes = encodedBytes;
    }
}

public class UTF8Mensaje : Mensaje
{
    public UTF8Mensaje(string mensaje)
       : base(mensaje)
    { }

   public override void Mostrar()
   {
        Console.WriteLine("Mensaje orginal:" + this.MensajeOriginal);
        Console.WriteLine("Mensaje encriptado con UTF8:" + this.MensajeEncriptado);
        Console.WriteLine("-----------------------------------------------------------");
    }
}

public class MD5Factory : CodificadorFactory
{
    public override Encriptador CrearEncriptador()
    {
        return new MD5Encriptador();
    }

    public override Mensaje CrearMensaje(string mensaje)
    {
       return new MD5Mensaje(mensaje);
    }
}

public class MD5Encriptador : Encriptador
{
    private MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();

    public override void Encriptar(Mensaje m)
    {
       Byte[] encodedBytes = md5.ComputeHash(Encoding.Default.GetBytes(m.MensajeOriginal));
       for (int i = 0; i < encodedBytes.Length; i++)
       {
             m.MensajeEncriptado += encodedBytes[i].ToString("x2");
        }
        m.EncodedBytes = encodedBytes;
    }
}

public class MD5Mensaje : Mensaje
{
    public MD5Mensaje(string mensaje)
        : base(mensaje)
    { }

    public override void Mostrar()
    {
        Console.WriteLine("Mensaje orginal:" + this.MensajeOriginal);
        Console.WriteLine("Mensaje encriptado con MD5:" + this.MensajeEncriptado);
        Console.WriteLine("-----------------------------------------------------------");
    }
}

Desde nuestro “cliente” podemos invocar las distintas factories y utilizarlas de manera indistinta sin importar de que familia de encriptador en particular se trate:

class Program
{
    static void Main(string[] args)
    {
        CodificadorFactory utf8 = new UTF8Factory();
        var mensaje = utf8.CrearMensaje("Hola mundo!");
        var encriptador = utf8.CrearEncriptador();
        encriptador.Encriptar(mensaje);

        mensaje.Mostrar();

        CodificadorFactory md5 = new MD5Factory();
        mensaje = md5.CrearMensaje("Hola mundo!");
        encriptador = md5.CrearEncriptador();
        encriptador.Encriptar(mensaje);

        mensaje.Mostrar();

        Console.ReadLine();
    }
}

En la salida vemos que el resultado generado por los distintos encriptadores.

Aplicando abstract factory

Aplicando abstract factory

Adjunto el modelo de clases utilizado en el ejemplo para que puedan compararlo con el modelo de este patrón:

Modelo del ejemplo

Modelo del ejemplo

+ Ventajas

  • Aumenta la flexibilidad de la aplicación, mejores tiempos de compilación y ejecución.
  • Mejora el testing de la aplicación.

– Desventajas

  • Una mala definición del modelo puede traer aparejados problemas en la implementación del mismo, sobre todo a medida que crecen las familias de objetos.