6

Sto usando Simple Injector, ma forse quello di cui ho bisogno è più di una risposta concettuale.Come, usando l'iniezione di dipendenza, si ottiene la configurazione da più fonti?

Ecco il punto, supponiamo di avere un'interfaccia con le mie impostazioni delle applicazioni:

public interface IApplicationSettings 
{ 
    bool EnableLogging { get; } 
    bool CopyLocal { get; } 
    string ServerName { get; } 
} 

Poi, si sarebbe di solito hanno una classe che implementa IApplicationSettings, ottenendo ogni campo da una fonte specifica, per esempio:

public class AppConfigSettings : IApplicationSettings 
{ 
    private bool? enableLogging; 
    public bool EnableLogging 
    { 
     get 
     { 
      if (enableLogging == null) 
      { 
       enableLogging = Convert.ToBoolean(ConfigurationManager.AppSettings["EnableLogging"]; 
      } 
      return enableLogging; 
     } 
    } 
    ... 
} 

TUTTAVIA! Diciamo che voglio ottenere EnableLogging da app.config, CopyLocal dal database e ServerName da un'altra implementazione che ottiene il nome del computer corrente. Voglio essere in grado di combinare la configurazione della mia app senza dover creare 9 implementazioni, una per ciascuna combinazione.

Suppongo che non possa passare alcun parametro perché le interfacce sono state risolte dall'iniettore (contenitore).

ho pensato a questo, inizialmente:

public interface IApplicationSettings<TEnableLogging,TCopyLocal,TServerName> 
where TEnableLogging : IGetValue<bool> 
where TCopyLocal : IGetValue<bool> 
where TServerName : IGetValue<string> 
{ 
    TEnableLogging EnableLog{get;} 
    TCopyLocal CopyLocal{get;} 
    TServerName ServerName{get;} 
} 

public class ApplicationSettings<TEnableLogging,TCopyLocal,TServerName> 
{ 
    private bool? enableLogging; 
    public bool EnableLogging 
    { 
     get 
     { 
      if (enableLogging == null) 
      { 
       enableLogging = Container.GetInstance<TEnableLogging>().Value 
      } 
      return enableLogging; 
     } 
    } 
} 

Tuttavia, con questo ho un problema principale: come faccio a sapere come creare un'istanza di TEnableLogging (che è un IGetValue<bool>)? Supponiamo che IGetValue<bool> sia un'interfaccia che abbia una proprietà Value, che sarà implementata dalla classe concreta. Ma la classe concreta potrebbe aver bisogno di alcuni dettagli (come quello che è il nome della chiave in app.config) o meno (potrei semplicemente voler tornare sempre vero).

Sono relativamente nuovo all'iniezione di dipendenza, quindi forse sto pensando in modo sbagliato. Qualcuno ha qualche idea su come realizzare questo?

(Puoi rispondere utilizzando un'altra libreria DI, non mi importa. Credo di aver solo bisogno di afferrare il concetto di esso.)

risposta

11

Vi sono sicuramente muovendo nella direzione sbagliata qui.

Alcuni anni fa ho creato un'applicazione che conteneva un'interfaccia simile al tuo IApplicationSettings. Credo di averlo chiamato IApplicationConfiguration, ma conteneva anche tutti i valori di configurazione dell'applicazione.

Sebbene mi abbia aiutato a rendere testabile la mia applicazione in un primo momento, dopo un po 'di tempo il design ha iniziato a mettersi in mezzo. Molte implementazioni dipendevano da quell'interfaccia, ma continuava a cambiare molto e con essa l'implementazione e la versione di prova.

Proprio come te ho implementato un caricamento lento, ma questo ha avuto un lato negativo terribile. Quando mancava uno dei valori di configurazione, ho scoperto solo che è accaduto quando il valore è stato chiamato per la prima volta. Ciò ha comportato una configurazione che era hard to verify.

Ci sono voluti un paio di iterazioni di refactoring quale fosse il nocciolo del problema. Le grandi interfacce sono un problema. La mia classe IApplicationConfiguration stava violando lo Interface Segregation Principle e il risultato era una scarsa manutenzione.

Alla fine ho scoperto che questa interfaccia era completamente inutile.Oltre a violare l'ISP, quei valori di configurazione descrivevano un dettaglio dell'implementazione e invece di creare un'astrazione ampia, era molto meglio fornire ogni implementazione direttamente con il valore di configurazione di cui avevano bisogno.

Quando si esegue questa operazione, la cosa più semplice da fare è passare il valore di configurazione come valore della proprietà. Con Simple Injector è possibile utilizzare il metodo RegisterInitializer per questo:

var enableLogging = 
    Convert.ToBoolean(ConfigurationManager.AppSettings["EnableLogging"]); 

container.RegisterInitializer<Logger>(logger => 
{ 
    logger.EnableLogging = enableLogging; 
}); 

Nel fare questo, il valore enableLogging viene letto solo una volta dal file di configurazione e viene fatto in modo durante l'avvio dell'applicazione. Ciò lo rende veloce e lo fa fallire all'avvio dell'applicazione quando manca il valore.

Se per qualche motivo è necessario ritardare la lettura (da database per esempio), è possibile utilizzare Lazy<T>:

Lazy<bool> copyLocal = new Lazy<bool>(() => 
    container.GetInstance<IDatabaseManager>().RunQuery(CopyLocalQuery)); 

container.RegisterInitializer<FileCopier>(copier => 
{ 
    copier.CopyLocal = copyLocal.Value; 
}); 

Invece di passare i valori utilizzando le proprietà, è anche possibile utilizzare argomenti del costruttore, ma questo è un po 'più difficile da raggiungere. Prendi un look at this article per alcune idee.

Se vi trovate a registrare lo stesso valore di configurazione per molte registrazioni, probabilmente mancate un'astrazione. Date un'occhiata a questo:

container.RegisterInitializer<UserRepository>(rep => { 
    rep.ConnectionString = connectionString; }); 
container.RegisterInitializer<OrderRepository>(rep => { 
    rep.ConnectionString = connectionString; }); 
container.RegisterInitializer<CustomerRepository>(rep => { 
    rep.ConnectionString = connectionString; }); 
container.RegisterInitializer<DocumentRepository>(rep => { 
    rep.ConnectionString = connectionString; }); 

In questo caso probabilmente si sta perdendo un'astrazione IDatabaseFactory o IDatabaseManager, e si dovrebbe fare qualcosa di simile:

container.RegisterSingle<IDatabaseFactory>(new SqlDatabaseFactory(connectionString)); 
+1

Grazie, che ha fatto un sacco di senso! Interessante che entrambi abbiamo fatto passi simili e abbiamo realizzato che qualcosa stava andando via nel modo in cui dovrebbe essere haha –

Problemi correlati