Programming Languages Hacks

Importanti regole per linguaggi di programmazione rilevanti come Java, C, C++, C#…

  • Subscribe

  • Lettori

    I miei lettori abituali

  • Twitter

Applicazioni Distribuite: Proprietà Statiche vs Caching. Singleton rivisitato

Posted by Ricibald on August 13th, 2008

Normalmente quando scriviamo codice capita di scrivere proprietà statiche: si pensi al pattern Singleton. Lo scopo di questo pattern è proprio assicurare la condivisione degli elementi, per ragioni di efficienza o di consistenza.

Il pattern funziona bene in contesti “normali”: è implementato mediante un campo/proprietà statico e quindi condiviso nell’ambito dell’applicazione Console/Windows che sviluppiamo. Se però ci spostiamo in contesti distribuiti il pattern non è in realtà così efficace:

In contesti distribuiti, i campi statici sono condivisi solo nell’ambito della stessa richiesta

Quindi in una nuova richiesta viene “dimenticato” il valore assunto degli stessi campi statici in altre richieste. I campi statici nascono invece con lo scopo di avere memoria globale di un’informazione. Quindi, come regola generale:

Il concetto di “campi static” dovrebbe sempre essere virtualizzato

Questo significa che, come possibile soluzione, la proprietà statica deve essere acceduta mediante una classe astratta CacheManager, che:

  1. è ottenibile come Singleton “puro”, cioè come proprietà statica a cui siamo tanto abituati. Avremo quindi un metodo CacheManager.GetInstance che restituisce tale istanza, condivisa solo nell’ambito della richiesta corrente
  2. consente metodi come SetPrinterSingleton o GetPrinterSingeton che restituiscono tali valori in cache
  3. l’implementazione di tale metodi varia a seconda del tipo concreto in uso

Il terzo punto significa che avremo diverse implementazioni, come StaticCacheManager o WebApplicationCacheManager, in cui la prima prenderà valori nel modo classico, tramite proprietà di istanza, mentre la seconda implementazione li prenderà dalle variabili “Application” del nostro web site.

La scelta di quale implementazione concreta utilizzare è demandata al momento in cui viene creato internamente l’istanza unica di CacheManager, che ci restituirà l’istanza corretta sulla base del principio “Dependency Injection: viene specificato cosa creare mediante file di configurazione e/o Reflection.

Molto comodo risulta infine utilizzare un Singleton con “shortcut”: invece di scrivere

public abstract class CacheManager {
    public static CacheManager GetInstance() {
        return ServiceLocator.GetCacheManager(); // usa reflection
    }
    public abstract Printer GetPrinterSingleton();
}

scriveremo una forma molto più compatta da utilizzare per il “client” della classe:

public abstract class CacheManager {
    public static CacheManager GetInstance() {
        return ServiceLocator.GetCacheManager(); // usa reflection
    }
    public static Printer GetPrinterSingeton() {
        return GetInstance().GetPrinterSingleton();
    }
    protected abstract Printer GetPrinterSingleton();
}

Che verrà quindi utilizzato in questo modo:

public sealed class Printer {
    private Printer() {
    }
    public static Printer GetInstance() {
        // lock regione critica omessi per semplicita'
        Printer printer = CacheManager.GetPrinterSingleton();
        if(printer == null) {
            // il set restituisce la stessa istanza passata per parametro
            printer = CacheManager.SetPrinterSingleton(new Printer());
        }
        return printer;
    }
}

Si può anche esprimere in una forma succinta, utilizzando l’operatore di null coalescing di C#:

public sealed class Printer {
    private Printer() {
    }
    public static Printer GetInstance() {
        return CacheManager.GetPrinterSingleton() ?? CacheManager.SetPrinterSingleton(new Printer());
    }
}

Si può infine completare l’implementazione garantendo la mutua esclusione utilizzando l’approccio di William Pugh:

public sealed class Printer {
    private Printer() {
    }
    public static Printer GetInstance() {
        return SingletonHolder.GetInstance();
    }

    class SingletonHolder {
        static SingletonHolder() { }
        static Printer GetInstance() {
            return CacheManager.GetPrinterSingleton() ?? CacheManager.SetPrinterSingleton(new Printer());
        }
    }
}

Attenzione: si potrebbe pensare che lo stesso concetto si applichi anche al pattern Flyweight. In realtà questo pattern (a differenza dell’esempio riportato nell’articolo in Wikipedia) non dovrebbe prevedere una mappa statica, ma a livello istanza, quindi la regola descritta è comunque soddisfatta.

In pratica la regola da seguire è questa:

Campi/proprietà statiche hanno senso solo nel contesto del pattern Singleton, e in tal caso il concetto di “campi static” deve essere virtualizzato tramite un CacheManager, a sua volta creabile tramite la versione “standard” del pattern Singleton

Leave a Reply

You must be logged in to post a comment.