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

8

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

Voglio migrare gradualmente il codice al contenitore di input delle dipendenze (nel mio caso è Guice , perché è un progetto GWT ). Dalla mia comprensione dell'iniezione di dipendenza, è tutto o niente. Tutte le classi sono gestite da Spring / Guice o none. Poiché il codebase è grande, non posso 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 @Inject in 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 contesto Injector / application globalmente disponibile attraverso un singleton per il momento, in modo che altre classi possano ottenere da esso un bean gestito. Ma contraddice l'idea importante di non rivelare il composition root all'applicazione.

Un altro approccio sarebbe di tipo bottom-up: per iniziare con le classi "di alto livello", includerle in un contenitore per le dipendenze e passare lentamente alle classi "più piccole". Ma poi devo aspettare molto tempo, dal momento che posso testare quelle classi più piccole che dipendono ancora da globali / statici.

Quale sarebbe il modo per ottenere una migrazione graduale?

P.S. La domanda Approcci graduali all'iniezione di dipendenza è simile nel titolo, ma non risponde alla mia domanda.

    
posta damluar 17.02.2016 - 10:47
fonte

2 risposte

2

Mi dispiace C# è la mia lingua preferita, posso leggere Java ma probabilmente maccherò la sintassi cercando di scriverlo ... Gli stessi concetti si applicano tra C# e Java , quindi spero che questo mostra i passaggi su come potresti spostare gradualmente il tuo codice base per essere più testabile.

Data:

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 refactorato per utilizzare DI, senza l'uso di un contenitore IOC, e potresti potenzialmente scomposizione in più passaggi:

(potenziale) Fase 1: prendere le dipendenze ma non modificare il 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 first refactor se si implementa il contenitore IOC e si modifica immediatamente il 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 essere tecnicamente fatto da solo - ma (potenzialmente) potrebbe essere molto più lavoro - dipende da quante classi stanno attualmente "facendo il punto" della funzionalità che stai cercando di DI.

Considera di andare con il passaggio 1 - > passaggio 2 - Sarai in grado di creare test unitari per Foo , indipendentemente da Bar . Mentre prima del refactor step 1 non era facilmente realizzabile senza utilizzare l'effettiva implementazione di entrambe le classi. Esegui il passaggio 1 - > il passaggio 2 (anziché il passaggio 2 immediatamente) consentirebbe modifiche più piccole nel tempo, e avrai già l'inizio di un'imbracatura di prova per garantire che il tuo refactore funzioni senza conseguenze.

    
risposta data 09.03.2016 - 18:48
fonte
0

Il concetto è lo stesso sia che tu stia usando Java, PHP o persino C #. Gemma Anible in questo video di YouTube tratta abbastanza bene questa domanda:

link (PHP, sorry!)

Sostituisci il codice non testabile con "facciata" (per mancanza di un termine migliore) che chiama un nuovo codice verificabile. Quindi, gradualmente, è possibile sostituire le chiamate meno recenti con i servizi iniettati. L'ho fatto in passato e funziona abbastanza bene.

    
risposta data 01.04.2016 - 17:27
fonte

Leggi altre domande sui tag