giovedì 17 luglio 2014

Come di refactoring per Dependency Injection, Parte 3: applicazioni di grandi dimensioni

Ondrej Balas continua la sua serie sul refactoring del codice per l'iniezione di dipendenza, concentrandosi sulle tecniche che rendono più facile refactoring di applicazioni complesse.

Con Ondrej Balas2014/07/16

Dependency Injection è un paradigma di programmazione che consente di scrivere codice molto più gestibile attraverso la promozione di accoppiamento lasco di oggetti. (Nel caso in cui li hai persi, questo articolo è una continuazione della Parte 1 e Parte 2 nella mia serie sulla dependency injection.) In una nuova o piccola applicazione, refactoring di Dependency Injection è relativamente semplice. Poiché l'applicazione di essere riorganizzata cresce più grande, tuttavia, può diventare piuttosto l'impresa. Fortunatamente, c'è un modo per semplificare il problema e refactoring tuo pezzo applicazione per pezzo.

Refactoring un'applicazione complessa
Prima di iniziare il refactoring, è necessario sapere che cosa cercare. Sono una persona molto visiva, così ogni volta che mi occupo di refactoring di un grande o complessa applicazione, comincio da lavagna tutte le componenti e le loro interazioni. Vedendo tutti i progetti sul tavolo mi permette di vedere facilmente dove il punto di ingresso nell'applicazione è, che è dove voglio mettere la mia Composizione Root.

Ho poi passo attraverso il codice, entrando in ogni costruttore, e cerco costruttori che nuovi oggetti in se stessi, come questo:

NotificationEngine pubblico ()
{
  _dataStream = new SomeDataStream ();
  _emailSender = new EmailSender ();
  . _recipientAddress = new ConfigurationReader () GetNotificationRecipientAddress ();
  _logger = new FileSystemLogger ("somepath.txt");
}
Quando vedo questo, è quasi garantito gli oggetti che vengono creati qui hanno i loro costruttori proprio come questo, creando una complessa catena di costruttori. E se questo non bastasse, gli oggetti sono probabilmente anche creati in altri luoghi, rendendo le cose ancora più confuse.

Come ho già detto in precedenza nella serie, l'obiettivo è quello di sostituire eventualmente costruttori come quella con i costruttori che prendono le loro dipendenze come parametri anziché crearli, come questo:

Pubblicità

NotificationEngine pubblico (IDataStream Datastream, IEmailSender EmailSender,
  IConfigurationReader configurationReader, ILogger logger)
{
  _dataStream = Datastream;
  _emailSender = EmailSender;
  _recipientAddress = configurationReader.GetNotificationRecipientAddress ();
  _logger = logger;
}
In un'applicazione complessa, questo è molto più facile a dirsi che a farsi. Una pietra comune passo è quello di creare la nuova costruzione, ma lasciare il costruttore predefinito originale al suo posto. Il costruttore originale deve quindi essere modificato per mettere in nuova costruzione, come questo:

NotificationEngine pubblico ()
    : Questa (nuova SomeDataStream (),
    nuovo EmailSender (),
    nuovo ConfigurationReader (),
    nuovo FileSystemLogger ("somepath.txt"))
  {
  }
Non interrompere qualsiasi codice esistente consente di refactoring dell'applicazione un pezzo alla volta. E quando si utilizza un Dependency Injection (DI) container per creare gli oggetti, la maggior parte dei contenitori selezionerà automaticamente il costruttore più complicata, piuttosto che quella di default (ne parleremo più avanti).

Questa tecnica, nota come "Bastard Injection," è controverso e generalmente considerato un anti-modello. Questo è perché è spesso usato solo come un modo per facilitare il test di unità, mentre i costruttori di default sono ancora gli unici costruttori utilizzati nel codice di produzione. Può anche causare il codice per creare implicitamente dipendenze assembly che non sarebbero altrimenti dovrebbe essere di riferimento. Ad esempio, se IEmailSender era un'interfaccia definita nello stesso progetto come costruttore, ma non era EmailSender, creando la EmailSender all'interno del costruttore creerebbe una dipendenza su tale altro gruppo.

Eppure, trovo Bastard iniezione di un ripiego incredibilmente utile in pratica - ma non dovrebbe essere l'obiettivo finale.

Una volta ho completato un primo passaggio attraverso l'applicazione, mi metto alla ricerca del "nuovo" parola chiave utilizzata in altri luoghi. Un'altra cosa comune che vedo è campi che hanno le nuove implementazioni dato a loro e nessun costruttore presente, come questo:

public class NotificationEngine
{
  readonly privato IDataStream _dataStream = new SomeDataStream ();
  readonly privato IEmailSender _emailSender = new EmailSender ();
  . stringa readonly privato _recipientAddress = new ConfigurationReader () GetNotificationRecipientAddress ();
  readonly privato ILogger _logger = new FileSystemLogger ("somepath.txt");
   
  / / Il resto della classe
}
Questo dovrebbe essere riscritta come se tutti gli oggetti di istanze stava avvenendo all'interno del costruttore predefinito.

Iniezione Setter
Finora ho usato Constructor Injection, che è di gran lunga la forma più comunemente usata di DI. A seconda dello scenario, altri modelli potrebbero essere più efficaci. Un tale modello, Iniezione Setter (noto anche come Injection proprietà), è utile quando c'è un default ragionevole e locale per la dipendenza da impostare. Prendete il codice in Listato 1 , per esempio.

Listato 1: Un'implementazione di iniezione Setter utilizzando gli attributi Ninject
privato IEmailSender EmailSender;
[Inject, Optional]
pubblico IEmailSender EmailSender
{
  ottenere
  {
    if (EmailSender == null)
    {
      EmailSender = new DefaultEmailSender ();
    }
    ritorno EmailSender;
  }
  set {EmailSender = value; }
}
Anziché impostare la proprietà EmailSender all'interno del costruttore, è etichettato con il Inject e gli attributi opzionali. L'attributo Inject dice Ninject per impostare subito che la proprietà dopo aver creato l'oggetto, ma che, da sola, potrebbe non essere sufficiente. Se l'attributo opzionale non è impostato anche, Ninject sarà un'eccezione se non c'è presente vincolante per IEmailSender. Grazie alla combinazione di entrambi gli attributi, è possibile creare una proprietà che è impostato solo se esiste un legame - altrimenti, verrà utilizzata la DefaultEmailSender.

Gestione di più Costruttori
Quando si tratta di più costruttori, è importante sapere quale sarà chiamato dal contenitore DI quando un oggetto del richiesto. Questo varia leggermente da contenitore a contenitore, ma il principio generale è che il contenitore utilizzerà il costruttore con il maggior numero di parametri può risolvere completamente. Ad esempio, si supponga di avere tre costruttori:

1: public Quirble ()
2: public Quirble (IFoo foo)
3: public Quirble (IFoo foo, bar IBar)
Se il contenitore ha solo vincolante per IFoo, ma non IBar, selezionerà costruttore No. 2. Se ha un legame sia IFoo e IBar, selezionerà costruttore No. 3. Se non ha né vincolante, selezionerà il costruttore predefinito (n. 1). Con Ninject, è possibile forzare un costruttore specifico per essere chiamato utilizzando l'attributo Inject:

1: [Inject] Quirble pubblico ()
Utilizzando l'attributo Inject, che stai dicendo Ninject usare sempre quel costruttore e ignorare tutti gli altri costruttori.

Ci sono alcuni casi particolari in cui alcuni contenitori non comportano lo stesso come gli altri. Prendete questo esempio:

1: public Quirble (IFoo foo)
2: public Quirble (IBar bar)
In questo caso, se il contenitore ha un legame sia per IFoo e IBar, Ninject genererà un'eccezione a causa di un costruttore ambigua. Nella stessa situazione, Castello di Windsor selezionerà uno dei costruttori. In realtà, secondo la sua documentazione , "Se ci sono più di un costruttore più golosi, si utilizzerà qualsiasi di essi. È indefinito che uno e non si deve dipendere da Windsor raccogliendo sempre lo stesso."

Scomposizione
Rifattorizzare un'applicazione complessa per utilizzare l'iniezione di dipendenza può sembrare complicata, ma con tecniche come Bastard iniezione può essere suddiviso in tante piccole refactoring. È inoltre possibile utilizzare tecniche come l'iniezione setter in luoghi dove il passaggio ad iniezione del costruttore sarà difficile. E sappiate che i contenitori differenti si comportano in modi diversi, quindi, quando you'e non so come si comporterà il contenitore, è sempre una buona idea di controllare la documentazione.

Up next: configurazione XML, uno sguardo a vari contenitori DI e come creare una architettura plug-in con l'uso del legame basato convenzione.
Corso Visual Studio - Corso asp.net
Corso C# - Corso PHP - Corso Joomla - Corsi asp.net - Corso Java

Nessun commento:

Posta un commento