inversion of control

Pattern Avanzati di Programmazione: Riutilizzo, Flessibilità e Architetture a Low Coupling

Nel mondo dello sviluppo moderno, la capacità di scrivere codice riutilizzabile, flessibile e facilmente testabile è un tratto distintivo degli sviluppatori senior. Architetture solide si basano su due principi cardine:

  • Separation of concerns
  • Inversion of Control (IoC)

In questo articolo esploreremo come usare Dependency Injection (DI) e IoC in modo avanzato, applicando pattern flessibili e reali in ambienti enterprise complessi.


1. Dependency Injection e IoC: Oltre le Basi

L’idea chiave è semplice: le dipendenze non devono essere istanziate all’interno della classe, ma fornite dall’esterno. Questo permette maggiore flessibilità, estendibilità e testabilità.

Esempio base:

csharpCopiaModificapublic interface IReportService
{
    void Generate();
}

public class PdfReportService : IReportService
{
    public void Generate() => Console.WriteLine("Generazione report PDF...");
}

public class ReportManager
{
    private readonly IReportService _service;

    public ReportManager(IReportService service) => _service = service;

    public void Process() => _service.Generate();
}

Vantaggi:

  • Facilità di test (mocking, sostituzioni)
  • Codice aperto a estensioni, chiuso a modifiche (principio OCP)
  • Low coupling tra classi

2. Uso di un IoC Container

In contesti reali, l’iniezione manuale è inefficiente. Un IoC container (come quello di ASP.NET Core) gestisce automaticamente la creazione e la risoluzione delle dipendenze.

Registrazione:

csharpCopiaModificaservices.AddScoped<IReportService, PdfReportService>();
services.AddScoped<ReportManager>();

Risoluzione:

csharpCopiaModificavar manager = serviceProvider.GetRequiredService<ReportManager>();
manager.Process();

👉 Scope disponibili:

  • Transient: nuova istanza ogni volta
  • Scoped: una per richiesta
  • Singleton: una per tutta l’applicazione

3. Binding Dinamico a Runtime

Spesso il comportamento dell’applicazione deve cambiare in base al contesto.

Soluzione: Factory con IoC

csharpCopiaModificapublic class ReportFactory
{
    private readonly IServiceProvider _provider;

    public ReportFactory(IServiceProvider provider) => _provider = provider;

    public IReportService Create(string type)
    {
        return type switch
        {
            "pdf" => _provider.GetRequiredService<PdfReportService>(),
            "html" => _provider.GetRequiredService<HtmlReportService>(),
            _ => throw new NotSupportedException()
        };
    }
}

Questo approccio centralizza la logica e rende le decisioni configurabili.


4. Decorator Pattern con DI

Hai bisogno di aggiungere funzionalità (come logging, caching) senza modificare l’implementazione originale? Usa un Decorator.

Esempio:

csharpCopiaModificapublic class LoggingReportService : IReportService
{
    private readonly IReportService _inner;

    public LoggingReportService(IReportService inner) => _inner = inner;

    public void Generate()
    {
        Console.WriteLine("LOG: Inizio...");
        _inner.Generate();
        Console.WriteLine("LOG: Fine.");
    }
}

Registrazione:

csharpCopiaModificaservices.AddScoped<PdfReportService>();
services.AddScoped<IReportService>(provider =>
    new LoggingReportService(provider.GetRequiredService<PdfReportService>())
);

5. Iniezione di Collezioni: Strategie Plugin-Based

Se hai più implementazioni della stessa interfaccia, puoi iniettarle tutte con IEnumerable<T>.

csharpCopiaModificapublic class CompositeReportService : IReportService
{
    private readonly IEnumerable<IReportService> _services;

    public CompositeReportService(IEnumerable<IReportService> services) => _services = services;

    public void Generate()
    {
        foreach (var service in _services)
            service.Generate();
    }
}

👉 Ottimo per pattern plugin e strategie di estensione modulari.


6. Feature Toggle e Strategie Condizionali

In ambienti complessi o multi-tenant è utile gestire configurazioni dinamiche tramite feature toggle.

csharpCopiaModificaservices.AddScoped<IReportService>(provider =>
{
    var config = provider.GetRequiredService<IOptions<AppSettings>>().Value;

    return config.UsePdf
        ? provider.GetRequiredService<PdfReportService>()
        : provider.GetRequiredService<HtmlReportService>();
});

7. Testing Avanzato con Mock

Una delle vere forze di DI è la facilità di testing. Con librerie come Moq:

csharpCopiaModificavar mock = new Mock<IReportService>();
mock.Setup(s => s.Generate()).Verifiable();

var manager = new ReportManager(mock.Object);
manager.Process();

mock.Verify(); // Verifica che sia stato chiamato

Test rapidi, isolati e affidabili.


8. Anti-Pattern da Evitare

  • Service Locator: rende le dipendenze nascoste e poco testabili.
  • Over-injection: se un costruttore ha troppe dipendenze, la classe è probabilmente violando il principio Single Responsibility.
  • Injection diretta di IServiceProvider: va bene solo in scenari controllati, come factory delegate.
  • Costruttori pesanti: mantienili leggeri, senza logica.

Conclusione

Dependency Injection e Inversion of Control sono strumenti essenziali per costruire architetture moderne, scalabili e mantenibili. Con pattern avanzati come decorator, factory dinamiche, plugin e feature toggle, è possibile progettare sistemi altamente adattabili senza sacrificare la semplicità del codice.

Un sistema ben progettato non solo funziona oggi, ma è pronto per evolversi domani.


Pubblicato

in

da

Tag: