En artículos anteriores se introdujo el uso de Dependency Injection (DI) en aplicaciones WinUI 3 como un mecanismo para desacoplar dependencias y mejorar la mantenibilidad. Sin embargo, en aplicaciones reales, el uso básico de DI no es suficiente. A medida que el sistema crece, aparecen necesidades más complejas que requieren patrones avanzados.

Este artículo profundiza en patrones avanzados de Dependency Injection en aplicaciones de escritorio, abordando escenarios reales donde la simple inyección de servicios no es suficiente.

El objetivo es evolucionar desde un uso básico de DI hacia un enfoque arquitectónico sólido, alineado con aplicaciones enterprise.

El problema Link to heading

Errores comunes en el uso de DI:

  • Contenedor usado como Service Locator
  • Dependencias excesivas en ViewModels
  • Ciclos de vida mal definidos
  • Dificultad para manejar múltiples implementaciones
  • Falta de control sobre resolución dinámica

Ejemplo incorrecto Link to heading

var service = App.Services.GetService<IDataService>();

Problema:

  • rompe inversión de dependencias
  • difícil de testear
  • acoplamiento oculto

Principio clave Link to heading

DI no es solo registrar servicios. Es diseñar cómo las dependencias fluyen en la aplicación.

Patrón 1: Factory Pattern con DI Link to heading

Cuando se necesitan instancias dinámicas:

public interface IViewModelFactory
{
    T Create<T>();
}

public class ViewModelFactory : IViewModelFactory
{
    private readonly IServiceProvider _provider;

    public ViewModelFactory(IServiceProvider provider)
    {
        _provider = provider;
    }

    public T Create<T>()
    {
        return _provider.GetRequiredService<T>();
    }
}

Permite creación controlada.

Patrón 2: Scoped-like en desktop Link to heading

Aunque no existe scope web, se puede simular:

public class ScopeContext
{
    public Guid ScopeId { get; } = Guid.NewGuid();
}

Útil para:

  • workflows
  • sesiones temporales

Patrón 3: Named services Link to heading

Múltiples implementaciones:

services.AddTransient<IDataService, ApiDataService>();
services.AddTransient<IDataService, LocalDataService>();

Resolver manualmente:

public class DataServiceSelector
{
    public IDataService Resolve(bool useApi)
    {
        return useApi ? new ApiDataService() : new LocalDataService();
    }
}

Mejor opción: usar fábrica.

Patrón 4: Decorator Pattern Link to heading

Agregar comportamiento sin modificar implementación:

public class LoggingService : IDataService
{
    private readonly IDataService _inner;

    public LoggingService(IDataService inner)
    {
        _inner = inner;
    }

    public async Task GetDataAsync()
    {
        _logger.LogInformation("Inicio");
        await _inner.GetDataAsync();
        _logger.LogInformation("Fin");
    }
}

Útil para:

  • logging
  • caching
  • validación

Patrón 5: Lazy injection Link to heading

Evitar cargar dependencias pesadas:

public class ViewModel
{
    private readonly Lazy<IHeavyService> _service;

    public ViewModel(Lazy<IHeavyService> service)
    {
        _service = service;
    }
}

Mejora performance.

Patrón 6: Configuration-driven DI Link to heading

Seleccionar implementación por configuración:

if(config.UseApi)
    services.AddSingleton<IDataService, ApiDataService>();
else
    services.AddSingleton<IDataService, LocalDataService>();

Patrón 7: DI + Background services Link to heading

public class SyncService
{
    private readonly IDataService _data;

    public SyncService(IDataService data)
    {
        _data = data;
    }
}

Permite reutilizar lógica.

Patrón 8: DI + ViewModels complejos Link to heading

Evitar esto:

public class VM(A,B,C,D,E,F)

Solución:

  • agrupar servicios
  • usar facades

Patrón 9: Anti-patterns Link to heading

Evitar:

  • Service Locator
  • Singletons innecesarios
  • ViewModels con demasiadas dependencias
  • lógica dentro del contenedor

Problemas reales Link to heading

  • memoria mal gestionada
  • dependencias ocultas
  • dificultad para testear
  • código rígido

Estrategia profesional Link to heading

Un uso avanzado de DI incluye:

  • factories
  • decorators
  • control de ciclos de vida
  • configuración dinámica
  • diseño limpio

Buenas prácticas Link to heading

  • inyectar dependencias explícitamente
  • evitar Service Locator
  • controlar ciclos de vida
  • mantener DI simple
  • diseñar para testabilidad

Conclusión Link to heading

Dependency Injection en aplicaciones de escritorio va mucho más allá de registrar servicios en un contenedor. Es una herramienta de diseño que permite construir sistemas flexibles, mantenibles y escalables.

El uso de patrones avanzados permite manejar escenarios reales donde la complejidad crece y las decisiones arquitectónicas comienzan a tener impacto directo en la calidad del sistema.

Dominar estos patrones es un paso clave hacia un nivel arquitectónico profesional.