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

9

Tengo una gran base de código con muchos singletons "antipatrón", clases de utilidad con métodos estáticos y clases que crean sus propias dependencias usando newpalabras clave. 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 Guice, porque es un GWTproyecto). Desde mi entendimiento de la inyección de dependencia, es todo o nada. O bien Spring / Guice maneja todas las clases o ninguna. Como la base de código es grande, no puedo transformar el código durante la noche. Entonces necesito una forma de hacerlo gradualmente.

El problema es que cuando comienzo con una clase que necesita ser inyectada en otras clases, no puedo usar un simple @Injecten esas clases, porque esas clases aún no están administradas por el contenedor. Por lo tanto, esto crea una larga cadena hasta las clases "superiores" que no se inyectan en ninguna parte.

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

Otro enfoque sería de abajo hacia arriba: para comenzar con clases de "alto nivel", inclúyalas en el contenedor de inyección de dependencia y baje lentamente a 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 las variables globales / estáticas.

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

PD La pregunta Acercamientos graduales a la inyección de dependencia es similar en el título, pero no responde a mi pregunta.

Damluar
fuente
1
¿Desea pasar directamente de tener clases acopladas a usar un contenedor de inyección de dependencia? Primero debe realizar algunas refactorizaciones para desacoplar las clases, utilizando interfaces antes de siquiera pensar en casarse con cualquier marco o herramienta de inyección de dependencias.
Tulains Córdova
Lo suficientemente justo. Así que tengo alguna clase de utilidad A que se usa en otras 30 clases (una de ellas es la clase B), lo que las hace no verificables. Pensé hacer de la clase A una dependencia del constructor de la clase B. Tengo que cambiar todas las llamadas a los constructores y pasar A dentro. ¿Pero de dónde debería obtenerlo?
Damluar
Si B es la unidad más pequeña a la que puede aplicar DI para comenzar, entonces no veo ningún daño en construir A donde sea que esté construyendo B por el momento, siempre que A no tenga dependencias. Una vez que la pelota está rodando, puedes volver y refactorizarla.
Andy Hunt
@damluar ¿Una fábrica para empezar?
Tulains Córdova
1
@damluar una clase no se vuelve no comprobable solo porque depende de una clase de utilidad. Simplemente significa que su unidad es un poco más grande de lo que desea que sea. Si puede probar su clase de utilidad por sí mismo, puede usarla en las pruebas para las otras 30 clases sin preocuparse. Lea el blog de Martin Fowlers sobre pruebas unitarias.
gbjbaanb

Respuestas:

2

En este momento C#es mi idioma de su elección, puedo leer Javapero probablemente descuartizar la sintaxis tratando de escribirlo ... Los mismos conceptos se aplican entre C#y Javaaunque, por lo que espero que esto se mostrarán los pasos de cómo se puede mover gradualmente su base de código para ser 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 refactorizarse fácilmente para usar DI, sin el uso de un contenedor de COI, y podría incluso dividirlo en varios pasos:

(potencial) Paso uno: tome las dependencias pero no cambie 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 refactorizador 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 (potencialmente) sería mucho más trabajo, dependiendo de cuántas clases estén actualmente "renovando" la funcionalidad que está buscando para DI.

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

Kritner
fuente
0

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

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

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

Kevin G.
fuente