Programmazione

State Pattern in C#: guida decisionale con esempi pratici

Dario Fadda Aprile 26, 2026

Gli oggetti cambiano comportamento in base al loro stato interno continuamente. Un documento passa da bozza a revisione a pubblicato. Un personaggio di un gioco alterna tra inattivo, in corsa e in attacco. Un pagamento transita da pending ad autorizzato a catturato. La domanda quando usare lo State Pattern in C# emerge nel momento in cui la logica condizionale inizia a ramificarsi sullo stesso campo stato in metodo dopo metodo — e ogni nuovo stato obbliga a toccare più file.

In questo articolo troverete una guida decisionale pratica per il pattern State in C#: quando merita davvero il suo posto e quando soluzioni più semplici — enum o flag booleani — bastano e avanzano.

Cos’è lo State Pattern e cosa fa davvero

Lo State Pattern consente a un oggetto di cambiare il proprio comportamento quando il suo stato interno cambia. Dall’esterno sembra che l’oggetto abbia cambiato classe. Invece di sparpagliare istruzioni if e switch in ogni metodo che dipende dallo stato corrente, si incapsula il comportamento di ciascuno stato in una propria classe. L’oggetto delega al state object attivo in quel momento.

La struttura coinvolge tre ruoli:

  • Context — mantiene un riferimento allo state object corrente e vi delega il comportamento
  • State interface — dichiara i metodi che ogni stato deve implementare
  • Concrete state classes — implementano l’interfaccia con il comportamento specifico del loro stato

Quando avviene una transizione, il context sostituisce il riferimento al proprio state object. Questo approccio elimina i blocchi condizionali che crescono ogni volta che si aggiunge uno stato nuovo.

Segnali che indicano che avete bisogno dello State Pattern

Non ogni oggetto con un campo status ha bisogno dello State Pattern. Tuttavia certi code smell sono segnali forti che il pattern ripulirà il design.

La logica condizionale si ramifica sullo stesso stato ovunque

Questo è il trigger principale. Quando vedete lo stesso switch o if-else che controlla _status in tre, quattro o dieci metodi diversi, il vostro oggetto sta gestendo le transizioni di stato nel modo più difficile. Ogni nuovo stato significa toccare ciascuno di quei metodi.

Avete regole di transizione complesse

Se le transizioni valide dipendono dallo stato attuale — e alcune azioni devono lanciare eccezioni o essere semplicemente ignorate a seconda di dove ci si trova — il pattern State rende queste regole esplicite invece di affogarle in condizionali.

Ogni stato ha un comportamento distinto

Quando lo stato influenza come si comporta un metodo, non soltanto se eseguirlo, il pattern vale l’investimento. Ciascuna classe stato diventa un luogo coeso che racchiude tutto il comportamento per quella condizione.

Scenario 1: gestione degli ordini (comportamento condizionale complesso)

Ecco un esempio di elaborazione ordini con lo State Pattern:

public interface IOrderState
{
    void Submit(OrderContext context);
    void Cancel(OrderContext context);
    void Ship(OrderContext context);
    void Deliver(OrderContext context);
}

public sealed class OrderContext
{
    public IOrderState CurrentState { get; private set; }
    public string OrderId { get; }

    public OrderContext(string orderId)
    {
        OrderId = orderId;
        CurrentState = new PendingState();
    }

    public void TransitionTo(IOrderState state)
    {
        Console.WriteLine(
            $"Order {OrderId}: " +
            $"{CurrentState.GetType().Name} -> " +
            $"{state.GetType().Name}");
        CurrentState = state;
    }

    public void Submit() => CurrentState.Submit(this);
    public void Cancel() => CurrentState.Cancel(this);
    public void Ship()   => CurrentState.Ship(this);
    public void Deliver() => CurrentState.Deliver(this);
}

public sealed class PendingState : IOrderState
{
    public void Submit(OrderContext context)
    {
        Console.WriteLine("Ordine inviato per elaborazione.");
        context.TransitionTo(new ProcessingState());
    }

    public void Cancel(OrderContext context)
    {
        Console.WriteLine("Ordine annullato prima dell'invio.");
        context.TransitionTo(new CancelledState());
    }

    public void Ship(OrderContext context) =>
        throw new InvalidOperationException("Impossibile spedire un ordine in attesa.");

    public void Deliver(OrderContext context) =>
        throw new InvalidOperationException("Impossibile consegnare un ordine in attesa.");
}

Notate come PendingState sappia esattamente cosa fare (o non fare) per ogni azione. Non c’è nessuno switch nello stato: il polimorfismo gestisce tutto.

Scenario 2: workflow e gestione dei processi

Un caso d’uso classico è la gestione di una domanda con audit trail integrato:

public interface IApplicationState
{
    string StatusName { get; }
    void Review(ApplicationContext context);
    void Approve(ApplicationContext context);
    void Reject(ApplicationContext context);
    void RequestInfo(ApplicationContext context);
}

public sealed class ApplicationContext
{
    public IApplicationState CurrentState { get; private set; }
    public string ApplicantName { get; }
    public List<string> AuditLog { get; } = new();

    public ApplicationContext(string applicantName)
    {
        ApplicantName = applicantName;
        CurrentState = new SubmittedState();
        Log("Domanda inviata");
    }

    public void TransitionTo(IApplicationState state)
    {
        Log($"Transizione a {state.StatusName}");
        CurrentState = state;
    }

    public void Log(string message) =>
        AuditLog.Add($"[{DateTime.UtcNow:u}] {message}");
}

L’audit trail viene aggiornato automaticamente a ogni transizione, senza duplicazione di codice nei metodi chiamanti.

Scenario 3: gestione dello stato di un personaggio in un gioco

I giochi sono un esempio naturale: un personaggio che alterna tra IdleState, RunningState, AttackingState e DyingState beneficia enormemente di questo pattern, poiché ciascuno stato ha logica di input e di update completamente diversa.

Quando NON usare lo State Pattern

Il pattern non è sempre la risposta giusta. Evitate di applicarlo nei seguenti casi:

  • Stati booleani semplici: se l’oggetto ha solo attivo e inattivo, un campo booleano è più chiaro e diretto.
  • Pochi stati senza transizioni significative: se avete due o tre stati con poco comportamento differenziato, gli enum bastano.
  • La logica di transizione è esterna all’oggetto: se le decisioni di cambio stato appartengono a un orchestratore esterno, il pattern State aggiunge complessità senza benefici.

State Pattern vs alternative: quando scegliere cosa

Un enum con un switch è la scelta giusta quando gli stati sono pochi, stabili e il comportamento differisce solo su una o due dimensioni. Appena gli stati crescono, il comportamento diverge significativamente per metodo, o le transizioni diventano complesse, è il momento di passare al pattern State.

Il pattern State non è la stessa cosa del pattern Strategy: Strategy cambia l’algoritmo usato per una singola operazione, mentre State cambia il comportamento complessivo dell’oggetto al variare della condizione interna. Possono tuttavia lavorare insieme: una transizione di stato può emettere eventi che degli Observer gestiscono.

Integrazione con Dependency Injection

Una domanda comune è se il pattern State si integri bene con la DI di ASP.NET Core. La risposta è sì, con qualche accorgimento: le classi stato concrete possono essere registrate nel contenitore DI, ma è consigliabile usare factory o ActivatorUtilities.CreateInstance per creare le istanze in modo da evitare cicli nel contenitore.

Conclusione

Lo State Pattern in C# risolve un problema preciso: oggetti il cui comportamento cambia radicalmente al variare dello stato interno, con transizioni complesse e comportamento specifico per stato. Prima di applicarlo, fate questa verifica rapida: contate quante volte controllate lo stesso campo di stato in metodi diversi. Se la risposta supera tre o quattro, probabilmente il pattern vi risparmierà mesi di manutenzione futura.

La regola d’oro rimane: preferite sempre la soluzione più semplice che risolve il problema. Un enum con uno switch è più leggibile di una gerarchia di classi per casi banali. Ma quando la complessità cresce, lo State Pattern offre un’architettura che scala senza sforzo.


Fonte originale: When to Use State Pattern in C#: Decision Guide with Examples — Dev Leader

💬 Unisciti alla discussione!


Questo è un blog del Fediverso: puoi trovare quindi questo articolo ovunque con @blog@spcnet.it e ogni commento/risposta apparirà qui sotto.

Se vuoi commentare su State Pattern in C#: guida decisionale con esempi pratici, utilizza la discussione sul Forum.
Condividi la tua esperienza, confrontati con altri professionisti e approfondisci i dettagli tecnici nel nostro 👉 forum community