En el desarrollo con ASP.NET Core, el Patrón de Constructor (Builder Pattern) es fundamental para crear objetos complejos de manera secuencial. Su esencia radica en separar la construcción de un objeto complejo de su representación, permitiendo configuraciones paso a paso. Esto es particularmente útil en marcos como ASP.NET Core, donde la configuración de componentes es frecuente y multifacética.
Campos de Aplicación en el Framework
ASP.NET Core emplea extensamente este patrón para gestionar la configuración compleja de diversos componentes. A continuación, se detallan aplicaciones comunes.
1. Construcción del Host de la Aplicación
El host es el contenedor de ejecución que administra el ciclo de vida, la configuración y los servicios de la aplicación. Su construcción es un proceso complejo que se simplifica mediante constructores como IHostBuilder.
public class Startup
{
public static async Task Main(string[] args)
{
var appHost = Host.CreateDefaultBuilder(args)
.ConfigureHostConfiguration(configuration =>
{
configuration.AddEnvironmentVariables(prefix: "APP_");
})
.ConfigureServices((context, serviceCollection) =>
{
serviceCollection.AddHostedService<MiServicioEnSegundoPlano>();
serviceCollection.AddTransient<IServicioProcesamiento, ServicioProcesamiento>();
})
.Build();
await appHost.RunAsync();
}
}
Las configuraciones se encadenan porque cada método devuelve la misma instancia del construcotr, facilitando una configuración legible y modular.
2. Configuración de Clientes HTTP
La creación de clientes HTTP configurados (con políticas, manejadores de mensajes, etc.) se gestiona mediante IHttpClientBuilder.
public void ConfigureServices(IServiceCollection servicios)
{
servicios.AddHttpClient("ClienteExterno", cliente =>
{
cliente.BaseAddress = new Uri("https://servicio-externo.com/api/");
})
.AddPolicyHandler(PoliticasReintentos.ObtenerPoliticaReintentos())
.AddHttpMessageHandler(() => new ManejadorAutenticacion(token));
}
3. Configuración de Opciones
El patrón de Opciones (Options Pattern) utiliza constructores para vincular y validar configuraciones.
public void ConfigureServices(IServiceCollection servicios)
{
servicios.AddOptions<ConfiguracionEmail>()
.Bind(contexto.Configuration.GetSection("Email"))
.Validate(config => !string.IsNullOrEmpty(config.Servidor), "El servidor no puede estar vacío")
.ValidateOnStart();
}
Ventajas del Patrón Constructor
- Simplificación de Configuraciones Complejas: Descompone un proceso complejo en pasos manejables y claros.
- Alta Flexibilidad: Permite agregar o modificar pasos de configuración sin alterar el código base del constructor, siguiendo el principio de abierto/cerrado.
- Encadenamiento Fluido: La devolución del propio constructor (
return this;) permite una sintaxis declarativa y fácil de leer. - Separación de Responsabilidades: El cliente solo define qué se necesita configurar, no cómo se ensambla internamente el objeto.
Ejemplo de Implementación Personalizada
Para comprender el patrón a nivel fundamental, podemos implementar un constructor para una entidad de dominio, como una tarea de trabajo.
public class TareaTrabajo
{
public string Titulo { get; }
public string Descripcion { get; }
public DateTime FechaLimite { get; }
public int Prioridad { get; }
public List<string> Etiquetas { get; }
public TareaTrabajo(string titulo, string descripcion, DateTime fechaLimite, int prioridad, List<string> etiquetas)
{
Titulo = titulo;
Descripcion = descripcion;
FechaLimite = fechaLimite;
Prioridad = prioridad;
Etiquetas = etiquetas;
}
}
public class ConstructorTarea
{
private string _titulo;
private string _descripcion;
private DateTime _fechaLimite = DateTime.MaxValue;
private int _prioridad = 5;
private readonly List<string> _etiquetas = new List<string>();
public ConstructorTarea ConTitulo(string titulo)
{
_titulo = titulo;
return this;
}
public ConstructorTarea ConDescripcion(string descripcion)
{
_descripcion = descripcion;
return this;
}
public ConstructorTarea ConFechaLimite(DateTime fecha)
{
_fechaLimite = fecha;
return this;
}
public ConstructorTarea ConPrioridad(int nivel)
{
_prioridad = Math.Clamp(nivel, 1, 10);
return this;
}
public ConstructorTarea AgregarEtiqueta(string etiqueta)
{
if (!_etiquetas.Contains(etiqueta))
_etiquetas.Add(etiqueta);
return this;
}
public TareaTrabajo Construir()
{
// Lógica de validación antes de la construcción final
if (string.IsNullOrWhiteSpace(_titulo))
throw new InvalidOperationException("El título es obligatorio.");
return new TareaTrabajo(_titulo, _descripcion, _fechaLimite, _prioridad, new List<string>(_etiquetas));
}
}
// Uso del constructor personalizado
var tareaUrgente = new ConstructorTarea()
.ConTitulo("Corregir error crítico")
.ConDescripcion("Resolver el bug #123 en el módulo de pagos.")
.ConPrioridad(1)
.ConFechaLimite(DateTime.UtcNow.AddDays(1))
.AgregarEtiqueta("urgente")
.Construir();