Progettazione IoC / DI per la libreria di classi

6

Sto rifattando e introducendo i test unitari in una grande applicazione.

Al momento è una raccolta di classi statiche con metodi statici che restituiscono dati, ad esempio:

// in data access project
public static class DataStock {
     public static Stock GetStock(int operation) { return my data; }
}

// in business layer project
public static class Stock {
     public static Stock GetStock(int operation) {
         var stock = DataStock.GetStock(operation);
         // do something with the data
         return stock;
     }
}

// in forms project
public class FrmDisplayStock {
     public void LoadStock() {
         var stock = Stock.GetStock(this.selectorOperation);
         // display the stock
     }
}

Sto riprogettando l'app in questo modo:

// in data access project
public class DataStock : IDataStock {
     public Stock GetStock(int operation) { return my data; }
}

// in business layer project
public class Stock : IStock {
     private IDataStock data;

     public Stock() { this.data = new DataStock(); }
     public Stock(IDataStock data) { this.data = data; }

     public Stock GetStock(int operation) {
         var stock = data.GetStock(operation);
         // do something with the data
         return stock;
     }
}

// in forms project
public class FrmDisplayStock {
     private IStock stockbll;

     public FrmDisplayStock() { this.stockbll = new Stock(); }
     public FrmDisplayStock(IStock stockbll) { this.stockbll = stockbll; }

     public void LoadStock() {
         var stock = stockbll.GetStock(this.selectorOperation);
         // display the stock
     }
}

Questo mi consente di scrivere test per l'azienda e livelli di moduli, prendendo in giro il livello sottostante (non sto ancora testando il livello di accesso ai dati, ma è sia più complicato da testare che meno interessante).

Vorrei utilizzare un contenitore IoC in modo da non dover collegare manualmente le interfacce a tipi concreti ogni volta; con nidificazione profonda e molti test da scrivere, diventa piuttosto noioso e soggetto a errori

Il problema è che non capisco come implementarlo, tecnicamente.

  • Devo scrivere un bootstrapper per ogni progetto che collega il livello sottostante?
  • Devo cambiare i miei costruttori per ottenere l'istanza del contenitore come tale? %codice%
  • Funziona diversamente da come sto assumendo?
posta thomasb 30.06.2015 - 15:34
fonte

1 risposta

11

Vuoi che tutte le dipendenze vengano iniettate dal livello superiore assoluto, quindi dovresti avere un grafico di dipendenza con una radice e la radice ha un riferimento all'implementazione concreta dei deps in ogni nodo, registra quella concrezione per l'interfaccia in un contenitore, quindi afferra solo i bisogni di primo livello it dal contenitore. Ma nel fare ciò, quel livello ha deps i costrutti del contenitore che ha deps il costrutto del contenitore ...

Pensa al tuo grafico delle dipendenze costruito dal contenitore in questo modo:

public void main()
{
    var frmDisplayStock = new FrmDisplayStock(
      new Stock(
        new DataStock()
      )
    );
}

Tranne che invece di costruire manualmente ciascuna dipendenza su ogni livello del grafico, devi solo registrare i tipi con un contenitore (o comunque il contenitore che scegli ti richiede di registrarli). Ad esempio:

public void main()
{
    var container = new SomeIocContainer();
    container.Register<IDataStock>(typeof(DataStock));
    container.Register<IStock>(typeof(Stock));
    container.Register<IDataStock>(typeof(DataStock));
    var frmDisplayStock = container.Get<FrmDisplayStock>();
}

Nell'esempio sopra, l'ultima chiamata .Get utilizzerà i tipi di cui è a conoscenza e il contenitore probabilmente presumerà quali tipi sono necessari per soddisfare l'intero grafico delle dipendenze, in modo identico a quanto è stato fatto nella nostra precedente esempio senza contenitore.

Quindi, in conclusione, vuoi andare nel punto più alto del tuo stack e lasciare che decida tutte le concrezioni. Non volete avere quei costruttori pubblici predefiniti che conoscono le concrezioni, perché ciò causa riferimenti strettamente accoppiati tra cose quando un'interfaccia sarebbe più che sufficiente. Non sai mai quando utilizzare un IDataStock che funzioni su una cache ad alte prestazioni, su un webservice di terze parti o su un file locale, quindi non presumere che DataStock sia l'unica implementazione che utilizzerai. Il punto chiave per prendere questa decisione al livello superiore è, se lo fai al di sotto di questo, troverai quel livello di dipendenze sopra quel punto e ora avrai come riferimento su lo stack per definirlo?

Le dipendenze devono essere un grafo diretto senza loop (DAG) e una singola radice (albero), in un albero non si lasciano mai che i peer si conoscano l'un l'altro, e non si fa mai conoscere ai figli i loro genitori. Quindi dove, ma alla radice, puoi fare riferimento a tutte le dipendenze senza infrangere le regole di un albero?

Un altro esempio più ampio che può rendere tutto più chiaro poiché il tuo esempio ha solo 2 dipendenze, non molto di un campione per illustrare un concetto.

public void Main()
{
    var myWidgetsService = new WidgetService(
      new WidgetEventsProcessor(
        new MessageQueueSubscriber(ConfigurationManager.AppSettings["MQConnectionString"])
      ),
      new WidgetInProcessCacheStore(),
      new WidgetSerializerService(
        new JsonSerializer()
      ),
      new WidgetAccessControlService(
        new AccessRightsProcessor(
          new AccessRightsServiceClient(ConfigurationManager.AppSettings["AccessRightsServiceUri"])
        ),
        new AuthenticationProcessor(
          new WindowsAuthenticationChecker(ConfigurationManager.AppSettings["DomainController"]),
          new EMailClient(ConfigurationManager.AppSettings["SMTPService"]) // for auth failure warnings
        )
      )
    );
}

Dato l'albero delle dipendenze di cui sopra, devi solo effettuare le registrazioni e lasciare che il contenitore gestisca la costruzione (API IoC completamente creata, non una API di framework IoC reale o suggerita):

public void Main()
{
    var container = new SomeIocContainer();
    container.Register<IWindowsAuthenticationChecker>(typeof(WindowsAuthenticationChecker), ConfigurationManager.AppSettings["DomainController"]);
    container.Register<IEMailClient>(typeof(EMailClient), ConfigurationManager.AppSettings["SMTPService"]);
    container.Register<IAuthenticationProcessor>(typeof(AuthenticationProcessor));
    container.Register<IAccessRightsServiceClient>(typeof(AccessRightsServiceClient), ConfigurationManager.AppSettings["AccessRightsServiceUri"]);
    container.Register<IAccessRightsProcessor>(typeof(AccessRightsProcessor));
    container.Register<IWidgetAccessControlService>(typeof(WidgetAccessControlService));
    container.Register<IJsonSerializer>(typeof(JsonSerializer));
    container.Register<IWidgetSerializerService>(typeof(WidgetSerializerService));
    container.Register<IWidgetStore>(typeof(WidgetInProcessCacheStore));
    container.Register<IWidgetSerializerService>(typeof(WidgetSerializerService));
    container.Register<IMessageQueueSubscriber>(typeof(MessageQueueSubscriber), ConfigurationManager.AppSettings["MQConnectionString"]));
    container.Register<IWidgetEventsProcessor>(typeof(WidgetEventsProcessor));

    var myWidgetsService = container.Get<WidgetService>();
}
    
risposta data 30.06.2015 - 15:51
fonte

Leggi altre domande sui tag