Spostare gradualmente la base di codici nel contenitore di iniezione delle dipendenze


9

Ho una base di codice di grandi dimensioni con molti singleton "anti-pattern", classi di utilità con metodi statici e classi che creano le proprie dipendenze usando la newparola chiave. Rende molto difficile testare un codice.

Voglio migrare gradualmente il codice nel contenitore di iniezione delle dipendenze (nel mio caso Guice, perché è un GWTprogetto). Dalla mia comprensione dell'iniezione di dipendenza, è tutto o niente. O tutte le classi sono gestite da Spring / Guice o nessuna. Poiché la base di codice è grande, non riesco a trasformare il codice durante la notte. Quindi ho bisogno di un modo per farlo gradualmente.

Il problema è che quando inizio con una classe che deve essere iniettata in altre classi, non posso usare un semplice @Injectin quelle classi, perché quelle classi non sono ancora gestite dal contenitore. Quindi questo crea una lunga catena fino alle classi "top" che non vengono iniettate da nessuna parte.

L'unico modo che vedo è di rendere un Injectorcontesto / application globalmente disponibile attraverso un singleton per il momento, in modo che altre classi possano ottenere un bean gestito da esso. Ma contraddice l'idea importante di non rivelare composition rootl'applicazione.

Un altro approccio sarebbe dal basso verso l'alto: iniziare con classi "di alto livello", includerle nel contenitore di iniezione di dipendenza e spostarsi lentamente verso le classi "più piccole". Ma poi devo aspettare molto tempo, dal momento che posso testare quelle classi più piccole che dipendono ancora dai globali / statici.

Quale sarebbe il modo per ottenere una migrazione così graduale?

PS La domanda L'approccio graduale all'iniezione di dipendenza è simile nel titolo, ma non risponde alla mia domanda.


1
Vuoi passare direttamente dall'avere classi accoppiate all'utilizzo di un contenitore di iniezione di dipendenza? Devi prima fare del refactoring per disaccoppiare le classi, usando le interfacce prima ancora di pensare a sposarti con qualsiasi framework o strumento di iniezione di dipendenza.
Tulains Córdova,

Giusto. Quindi ho una classe di utilità A che viene utilizzata in altre 30 classi (una delle quali è la classe B), rendendole non verificabili. Ho pensato di rendere la classe A una dipendenza del costruttore della classe B. Devo cambiare tutte le chiamate ai costruttori e passare A all'interno. Ma da dove dovrei prenderlo?
damluar,

Se B è l'unità più piccola a cui puoi applicare DI per iniziare, allora non vedo alcun danno nel costruire A ovunque tu stia costruendo B per il momento - purché A non abbia dipendenze. Una volta che la palla sta rotolando, puoi tornare indietro e riformattare.
Andy Hunt,

@damluar Una fabbrica per cominciare?
Tulains Córdova,

1
@damluar una classe non diventa non verificabile solo perché si basa su una classe di utilità. Significa solo che la tua unità è leggermente più grande di quanto desideri. Se riesci a testare la tua classe di utilità da sola, puoi usarla nei test per tutte le altre 30 classi senza preoccupazioni. Leggi il blog di Martin Fowlers sui test unitari.
gbjbaanb,

Risposte:


2

Mi dispiace C#è la mia lingua preferita, posso leggere Javama probabilmente macellerei la sintassi cercando di scriverla ... Gli stessi concetti si applicano tra C#e Javaperò, quindi spero che questo mostrerà i passaggi su come spostare gradualmente la base di codice per essere più verificabile.

Dato:

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = new Foo();
        foo.DoStuff();
    }
}

public class Foo
{

    public void DoStuff()
    {
        Bar bar = new Bar();
        bar.DoSomethingElse();
    }

}

public class Bar
{
    public void DoSomethingElse();
}

potrebbe essere facilmente sottoposto a refactoring per l'utilizzo di DI, senza l'uso di un contenitore IOC, e potresti anche suddividerlo in più passaggi:

(potenziale) Passaggio uno: accetta le dipendenze ma nessuna modifica al codice di chiamata (UI):

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = new Foo();
        foo.DoStuff();
    }
}

public class Foo
{

    private IBar _iBar;

    // Leaving this constructor for step one, 
    // so that calling code can stay as is without impact
    public Foo()
    {
        _iBar = new Bar();
    }

    // simply because we now have a constructor that take's in the implementation of the IBar dependency, 
    // Foo can be much more easily tested.
    public Foo(IBar iBar)
    {
        _iBar = iBar;
    }

    public void DoStuff()
    {
        _iBar.DoSomethingElse();
    }

}

public interface IBar
{
    void DoSomethingElse();
}

public class Bar
{
    public void DoSomethingElse();
}

Refactor 2 (o primo refactor in caso di implementazione del contenitore IOC e modifica immediata del codice chiamante):

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = null // use your IOC container to resolve the dependency
        foo.DoStuff();
    }
}

public class Foo
{

    private IBar _iBar;

    // note we have now dropped the "default constructor" - this is now a breaking change as far as the UI is concerned.
    // You can either do this all at once (do only step 2) or in a gradual manner (step 1, then step 2)

    // Only entry into class - requires passing in of class dependencies (IBar)
    public Foo(IBar iBar)
    {
        _iBar = iBar;
    }

    public void DoStuff()
    {
        _iBar.DoSomethingElse();
    }

}

Il passaggio 2 potrebbe tecnicamente essere eseguito da solo - ma sarebbe (potenzialmente) molto più lavoro - dipende da quante classi stanno attualmente "rinnovando" la funzionalità che stai cercando di DI.

Considera di procedere con il passaggio 1 -> passaggio 2 - per cui potresti creare test unitari Foo, indipendentemente da Bar. Considerando che prima del rifrattore passo 1 non è stato facilmente realizzato senza utilizzare l'implementazione effettiva di entrambe le classi. Effettuare il passaggio 1 -> il passaggio 2 (anziché il passaggio 2 immediatamente) consentirebbe piccole modifiche nel tempo e avrai già avviato un cablaggio di prova per garantire che il tuo refattore abbia funzionato senza conseguenze.


0

Il concetto è lo stesso sia che si utilizzi Java, PHP o anche C #. Questa domanda è trattata abbastanza bene da Gemma Anible in questo video di YouTube:

https://www.youtube.com/watch?v=Jccq_Ti8Lck (PHP, scusa!)

Sostituisci il codice non verificabile con "facciata" (per mancanza di un termine migliore) che chiama un nuovo codice verificabile. Quindi gradualmente puoi sostituire le chiamate più vecchie con i servizi iniettati. L'ho fatto in passato e funziona abbastanza bene.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.