Come gestire il passaggio di più dipendenze in una gerarchia di moduli

4

Quindi ho la mia applicazione composta da un numero di moduli in una gerarchia di moduli. Inoltre assumiamo che ogni modulo sia una classe e abbiamo un albero di classi in cui le classi in alto usano le classi sottostanti, per renderlo più semplice.

Un class A1 in fondo può dipendere da alcuni parametri di input. class B1 è sopra class A1 e sta creando e utilizzando istanze di class A1 . Pertanto deve passare le dipendenze necessarie dalle istanze di class A1 in esse. Se non è in grado di creare queste dipendenze da alcune operazioni, class B1 ora ha le dipendenze ma anche le dipendenze di class A1 . Più andiamo avanti e più queste dipendenze si sommeranno in modo che il 100% co_de sarà necessario conoscere tutte le dipendenze a meno che non possano essere create ad un livello inferiore.

Questo significa che se class il mio programma dipende dall'attuale class A1 , devo passare questo al% di primo livellotemperature che poi lo passa al prossimo class e così via fino a che non arriva proprio al parte inferiore in class . Se lo faccio, rendo esplicito lo stato ma significa anche che ho metodi o classi che prendono molti parametri.

Variabili

Che cosa succede se il class A1 può cambiare mentre l'applicazione è in esecuzione? C'è un modo per evitare di passare tutto l'albero delle classi senza dare uno stato esplicito? Come gestireste questo ragazzi?

Costanti

Cosa succede se temperature è una costante che non cambierà mai mentre il programma è in esecuzione? Questo ci dà più opzioni per evitare di passarlo sempre come argomento? Potrei vedere qualcuno che usa una configurazione globale (singleton) ma renderà più difficile testare correttamente?

Potrei anche passare non il temperature stesso ma un oggetto di configurazione. Ciò significherebbe temperature non riceve ad esempio un class B1 e un temperature parametro ma ottiene un oggetto di configurazione lo passa a airpressure e class B1 dove class B2 ha solo bisogno del class B1 e airpressure ha solo bisogno del class B2 . E 'un buon approccio? Quali sono i pro / contro?

    
posta valenterry 22.05.2014 - 16:12
fonte

1 risposta

3

Questo è esattamente il motivo per cui esistono contenitori DI. Ti darò un esempio per Castle Windsor, ma dovrebbe applicarsi ad altri contenitori, con alcune modifiche.

Definizioni di classe :

public class A1
{
    public A1(decimal temperature)
    {
        _temperature = temperature;
    }

    private readonly decimal _temperature;

    // what follows is specific to the possible methods that make use of temperature
}

public class B1
{
    public B1(A1 a1)
    {
        _a1 = a1;
    }

    private readonly A1 _a1;

    // possible methods that make use of a1
}

Configurazione contenitore :

// this assumes a C# environment
var temperature = Convert.ToDecimal(ConfigurationManager.AppSettings["temperature"]);

IWindsorContainer container = new WindsorContainer();
container.Register(Component.For<A1>().DependsOn(Property.ForKey<decimal>(temperature)));
container.Register(Component.For<B1>());
container.Register(Component.For<MainForm>());

Questo è un esempio molto semplice di cosa puoi fare con un contenitore. I tipi utilizzati dall'applicazione devono essere resi noti al contenitore. Quando viene chiesto di creare un'istanza, il contenitore cerca nel costruttore le dipendenze e le creerà dai tipi di cui è a conoscenza. Questo è chiamato Iniezione costruttore perché il costruttore è usato per specificare le dipendenze per una classe.

Digita risoluzione :

L'iniezione delle dipendenze introduce un concetto chiamato composizione root , che è il luogo in cui vengono messi insieme tutti i componenti della tua applicazione (la sezione di configurazione qui sopra è collocata qui). Il contenitore dovrebbe (sto usando dovrebbe perché ci sono alcune eccezioni a questa regola) essere usato esplicitamente solo nella composizione della radice.

Nessuna delle classi della tua applicazione che non sono correlate all'infrastruttura può avere il contenitore come dipendenza . Se hai bisogno di infrangere questa regola, allora è una chiara indicazione di un difetto di progettazione.

Supponiamo di avere un'applicazione desktop, con un modulo principale che utilizza la nostra classe B1 .

public class MainForm : Form
{
    public MainForm(B1 b1)
    {

    }

    ...

    // do something with b1
}

La radice di composizione per questa applicazione è il punto di ingresso (per le applicazioni .net windows il punto di ingresso è Program.cs ). Ho reso MainForm noto al contenitore nel codice di configurazione sopra. Tutto quello che devo fare ora è ottenere un'istanza di MainForm e mostrarlo all'utente.

public static class Program
{
    // container configuration goes here

    // type resolving follows
    var mainForm = container.Resolve<MainForm>();

    // show main form the the user

    ...
}

A questo punto, il contenitore cerca il suo elenco di tipi noti per MainForm , cerca nel costruttore qualsiasi dipendenza e tenta di risolverli. Si sposterà attraverso la gerarchia fino all'ultimo tipo. Al termine, avrà creato istanze per tutti i tipi nella gerarchia (se i tipi sono stati trovati nel contenitore).

Se la temperatura è variabile, vorrei usare una classe con un metodo semplice chiamato GetTemperature e iniettare quella classe ovunque ne abbia bisogno. Il metodo presumibilmente esegue una logica per recuperare la temperatura. Se il sistema deve rispondere alle variazioni di temperatura, le classi che dipendono da esso possono essere osservatori della classe che emette queste modifiche.

Se una classe dipende da più costanti di configurazione, allora è perfettamente corretto raggrupparle in una singola classe e passare quella classe come dipendenza dove è necessaria. Castle ha un adapter che aiuta in questo caso particolare.

Raccomando di leggere il libro di Mark Seemann (link Amazon) sull'iniezione delle dipendenze, è scritto molto bene ed è , a mio parere, il miglior libro sull'argomento.

    
risposta data 22.05.2014 - 16:59
fonte