En la Programación Orientada a Objetos, un constructor hace referencia a un tipo de método/función que se ejecuta siempre que instaciamos un objeto. Lo podemos entender en su sentido más literal, es decir, se suele utilizar cuando existen una serie de «materiales» (parámetros) que son requeridos para poder construir el objeto de la clase solicitada.
En una clase puede haber ninguno, uno o múltiples constructores (constructor overload).
Ejemplo de una clase con un único constructor:
public class Post
{
/// Indicamos las propiedades de la clase.
public string Title;
public DateTime CreatedOn;
/// Constructor con los parámetros requeridos al hacer new Post(...)
public Post(string title)
{
Title = title;
CreatedOn = DateTime.Now;
}
}
public class Program
{
public static void Main()
{
Post post = new Post("¿Qué es un constructor?"); // "Llamada" al constructor
Console.WriteLine($"{post.Title} - {post.CreatedOn.ToShortDateString()}");
// 💻 Consola: ¿Qué es un constructor? - 30/03/2022
}
}
Builder Pattern (Patrón Constructor)
En el contexto de patrones de diseño de software, el Patrón Constructor (Builder Pattern en inglés), es un tipo de patrón creacional que permite – valga la redundancia – construir objetos complejos por etapas. Suele usarse una variante del patrón conocido como Constructor Fluído (Fluent Builder en inglés) para poder encadenar los métodos de construcción.
La finalidad es permitir rellenar las propiedades que componen la clase según se vayan necesitando y añadir validadores para el contenido. Aparte ayuda a evitar el anti-patrón y code smell llamado «constructor telescópico», que es el abuso de la sobrecarga de constructores.
Ejemplo de una clase con el anti-patrón:
public class Hamburguesa
{
/// Indicamos las propiedades de la clase.
public int NumberOfPatties;
public bool HasLettuce;
public bool HasCheese;
public bool HasSauces;
public Hamburguesa(int numberOfPatties, bool hasLettuce)
{
NumberOfPatties = numberOfPatties;
HasLettuce = hasLettuce;
}
public Hamburguesa(int numberOfPatties, bool hasLettuce, bool hasCheese)
{
NumberOfPatties = numberOfPatties;
HasLettuce = hasLettuce;
HasCheese = hasCheese;
}
public Hamburguesa(
int numberOfPatties,
bool hasLettuce,
bool hasCheese,
bool hasSauces)
{
NumberOfPatties = numberOfPatties;
HasLettuce = hasLettuce;
HasCheese = hasLettuce;
HasSauces = hasSauces;
}
}
public class Program
{
public static void Main()
{
Hamburguesa hamburguesaSimple = new Hamburguesa(1, true);
Hamburguesa hamburguesaConQueso = new Hamburguesa(1, true, true);
Hamburguesa hamburguesaConQuesoYSalsa = new Hamburguesa(1, true, true, true);
}
}
Como se puede apreciar, se utiliza el Overload que mencionábamos previamente para poder crear hamburguesas con múltiples configuraciones. Imagino que ya se puede prever que este sistema sería una locura si tuviéramos que seguir añadiendo ingredientes 😅.
Aparte, también habría que poner condicionales y throws para escapar en caso de que se le pasasen datos erróneos, como puede ser que el número de capas de carne sea un número negativo.
Ejemplo de clase con el patrón Fluent Builder:
public class Hamburguesa
{
/// Indicamos las propiedades de la clase.
public int NumberOfPatties { get; private set;} = 1;
public bool HasLettuce { get; private set;}
public bool HasCheese { get; private set;}
public bool HasSauces { get; private set;}
private Hamburguesa() { }
public class FluentBuilder
{
private readonly Hamburguesa _hamburguesa = new Hamburguesa();
public FluentBuilder AddPatties(int numberOfPatties = 0)
{
_hamburguesa.NumberOfPatties = numberOfPatties <= 0 ?
_hamburguesa.NumberOfPatties+1 : numberOfPatties;
return this;
}
public FluentBuilder WithLettuce() {
_hamburguesa.HasLettuce = true;
return this;
}
public FluentBuilder WithCheese() {
_hamburguesa.HasCheese = true;
return this;
}
public FluentBuilder WithSauces() {
_hamburguesa.HasSauces = true;
return this;
}
public Hamburguesa Build() => _hamburguesa;
}
}
public class Program
{
public static void Main()
{
Hamburguesa hamburguesaSimple = new Hamburguesa.FluentBuilder()
.WithLettuce()
.Build();
Console.WriteLine($"¿Tiene lechuga? {(hamburguesaSimple.HasLettuce ? "Si" : "No")}");
// 💻 Consola: Si
}
}
Ahora ya cada función dentro de la clase anidada FluentBuilder puede tener sus propios validadores internos, e incluso realizar verificaciones previas a la función Build para revisar si todos los elementos requeridos existen previos a que se instancie el objeto.