Fai entrambe le cose, usando IoC
Dovresti fare entrambi , utilizzando un Inversion of Control container, che insieme al LSP fornirà una netta separazione delle preoccupazioni e faciliterà notevolmente i test delle unità automatizzate.
L'idea è di definire una classe "statica" (non propriamente) e di iniettarla nel costruttore ovunque sia necessaria. Il contenitore si occupa dell'istanzia per te, e puoi impostare le tue impostazioni come "singleton" o "single instance", che essenzialmente lo rendono come una classe statica, solo tu puoi stub e simulare la sua interfaccia. Non saresti in grado di farlo con una normale classe statica.
In questo esempio userò un contenitore IoC comune noto come Autofac (che è gratuito e disponibile su NuGet. Ce ne sono anche molti altri.) Per prima cosa definiamo un'interfaccia che espone le impostazioni di cui abbiamo bisogno:
public interface ISettings
{
string Setting1 { get; }
string Setting2 { get; }
}
Quindi definiamo un'implementazione concreta.
public class GlobalSettings : ISettings
{
//These are just examples. In production code, perhaps they are database calls.
public string Setting1 { get { return "Foo"; } }
public string Setting2 { get { return "Bar"; } }
}
Quindi aggiungiamo le nostre impostazioni a composizione root , insieme alla nostra applicazione:
public static IContainer CompositionRoot()
{
var builder = new ContainerBuilder();
builder.RegisterType<GlobalSettings>().As<ISettings>().SingleInstance();
builder.RegisterType<Application>();
return builder.Build();
}
Oh, a proposito, ecco l'applicazione. Questo è solo un esempio fittizio:
public class Application
{
private readonly ISettings _settings;
public Application(ISettings settings) //Injected automatically
{
_settings = settings;
}
public void Run()
{
Console.WriteLine("Setting1 = '{0}'", _settings.Setting1);
Console.WriteLine("Setting2 = '{0}'", _settings.Setting2);
}
}
Ora per eseguirlo, chiamiamo la composizione root per comporre il grafo dell'oggetto, chiediamo ad Autofac di darci un'applicazione e chiama Run()
. Il contenitore inietterà automaticamente le impostazioni e inietterà sempre la stessa istanza. È molto facile. Finisce per essere una riga di codice:
public static void Main()
{
CompositionRoot().Resolve<Application>().Run();
}
Ed ecco l'output da Applicazione:
Setting1 = 'Foo'
Setting2 = 'Bar'
Ecco un esempio operativo completo su DotNetFiddle .
Quanto sopra è uno schema molto comune e consiglio vivamente di impararlo. Inoltre, soddisferà sicuramente le tue tendenze OCD, poiché mantiene tutto molto organizzato.
Prestazioni
I mezzi con cui si forniscono le variabili dove sono necessari non sono suscettibili di spostare l'ago rispetto alle prestazioni. Tuttavia, strutturare il codice in questo modo farà due cose per aiutarti con le prestazioni:
-
Avendo un codice ben strutturato che è più semplice da mantenere, il tuo team di sviluppo avrà più tempo per concentrarsi su argomenti relativi alle prestazioni.
-
La possibilità di sostituire mock e stub significa che è possibile isolare parti di codice e testarle in modo indipendente, il che può aiutare a determinare da dove provengono i problemi di prestazioni. Ad esempio, se si isola il client del database e le cose si accelerano, è probabile che l'accesso ai dati sia il collo di bottiglia.
Ad esempio, diciamo che scopriamo che la nostra applicazione sta funzionando molto lentamente e sospettiamo che la logica di recupero dei dati nella nostra classe GlobalSettings stia causando il problema. Possiamo facilmente creare un mockup:
public class MockSettings : ISettings
{
//Dummy implementation without database calls
}
e registralo invece di GlobalSettings:
builder.RegisterType<MockSettings>().As<ISettings>().SingleInstance();
E ora quando eseguiamo l'applicazione, le chiamate al database non avvengono e possiamo dire quale parte del problema delle prestazioni è isolata e cosa rimane.