Mover gradualmente la base de código al contenedor de inyección de dependencia

8

Tengo una base de código grande con muchos singletons "anti-patrón", clases de utilidad con métodos estáticos y clases que crean sus propias dependencias usando la palabra clave new . Hace que un código sea muy difícil de probar.

Quiero migrar gradualmente el código al contenedor de inyección de dependencia (en mi caso es Guice , porque es un proyecto GWT ). Desde mi comprensión de la inyección de dependencia, es todo o nada. O todas las clases son administradas por Spring / Guice o ninguna. Dado que el código base es grande, no puedo transformar el código durante la noche. Así que necesito una manera de hacerlo gradualmente.

El problema es que cuando comienzo con una clase que se debe inyectar en otras clases, no puedo usar un simple @Inject en esas clases, porque esas clases aún no están administradas por contenedor. Por lo tanto, esto crea una cadena larga hasta las "principales" clases que no se inyectan en ningún lugar.

La única forma que veo es hacer que un contexto Injector / application esté disponible globalmente a través de un singleton por el momento, para que otras clases puedan obtener un bean administrado. Pero contradice la idea importante de no revelar el composition root a la aplicación.

Otro enfoque sería de abajo hacia arriba: para comenzar con clases de "alto nivel", inclúyalos en el contenedor de inyección de dependencia y muévase lentamente hacia clases "más pequeñas". Pero luego tengo que esperar mucho tiempo, ya que puedo probar esas clases más pequeñas que aún dependen de variables globales / estadísticas.

¿Cuál sería la manera de lograr una migración tan gradual?

P.S. La pregunta Enfoques graduales para la inyección de dependencia es similar en el título, pero no responde a mi pregunta.

    
pregunta damluar 17.02.2016 - 10:47

2 respuestas

2

Lo siento C# es mi idioma de elección, puedo leer Java pero probablemente eliminaría la sintaxis tratando de escribirlo ... Sin embargo, los mismos conceptos se aplican entre C# y Java , así que espero que esto muestra los pasos de cómo podrías mover gradualmente tu base de código para que sea más comprobable.

Dado:

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();
}

podría ser fácilmente refactorizado para usar DI, sin el uso de un contenedor IOC, y potencialmente podría incluso dividirlo en varios pasos:

(potencial) Paso uno: tome las dependencias pero no modifique el código de llamada (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 primer refactor si implementa el contenedor IOC y cambia el código de llamada inmediatamente):

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();
    }

}

El Paso 2 podría hacerse técnicamente por sí mismo, pero sería (potencialmente) mucho más trabajo, dependiendo de cuántas clases están "actualizando" la funcionalidad que está buscando en DI.

Considere ir con el paso 1 - > ruta del paso 2: podrás crear pruebas unitarias para Foo , independientemente de Bar . Mientras que antes del refactor del paso 1, no se realizaba fácilmente sin utilizar la implementación real de ambas clases. Haciendo el paso 1 - > el paso 2 (en lugar del paso 2 inmediatamente) permitiría cambios más pequeños a lo largo del tiempo, y ya tendrá su inicio de un arnés de prueba para garantizar que su refactor funcione sin consecuencias.

    
respondido por el Kritner 09.03.2016 - 18:48
0

El concepto es el mismo si está utilizando Java, PHP o incluso C #. Gemma Anible trata bastante bien esta pregunta en este video de YouTube:

enlace (PHP, ¡lo siento!)

Reemplaza el código no comprobable con "fachada" (por falta de un término mejor) que llama código nuevo y comprobable. Luego, gradualmente, puede reemplazar las llamadas anteriores con los servicios inyectados. He hecho esto en el pasado y funciona bastante bien.

    
respondido por el Kevin G. 01.04.2016 - 17:27

Lea otras preguntas en las etiquetas