¿Existen alternativas a la inyección de dependencia para clases sin estado?

7

Estoy trabajando en una aplicación en la que he diseñado clases para encajar en varios grupos:

  • Inmutable: inicializado a través de constructores, usa lenguaje de copia e intercambio (inc. mover), puede copiar en profundidad (es decir, clonar), solo tiene "captadores" (no "configuradores") e implementa operadores de comparación (==,! =);
  • Servicios: clases sin estado que tienen métodos que toman inmutables y / o servicios para realizar las funciones deseadas;
  • Constructores: fábricas, etc. para construir inmutables.

La prueba de la unidad en mis inmutables ha sido sencilla. Puedo usar la inyección de dependencia a través de los constructores. Esto significa que puedo intercambiar clases de prueba para asegurarme de que estoy haciendo pruebas unitarias (a diferencia de las pruebas de integración). Puedo usar los constructores para construir mis objetos de producción. Este elemento de mi diseño me satisface en términos de pruebas y producción.

Sin embargo, en mis servicios parece que solo puedo mantener estas clases sin estado y con unidad comprobable mediante el uso de la inyección de dependencia a través de los argumentos del método.

Para mis servicios, una función de ejemplo cambia de:

virtual unsigned long foo() const override final;

... a:

virtual unsigned long foo(const ISomeInterface & dependency) const override final;

Esto significa que mis clases de servicio son comprobables, pero ahora tengo que crear una instancia de las dependencias fuera de la clase al usar el código en producción. Por ejemplo:

// without dependency injection
Service service;
return service.foo();

// with dependency injection
Service service;
Dependency dependency;
return service.foo(dependency);

Esto ha llevado a un gran número de mis clases de servicio que ahora necesitan al menos 1 argumento más para cada método de clase. Nota: este es el enfoque que estoy usando actualmente en mi código.

Mi pregunta es esta:

¿Qué alternativas tengo a esta forma de inyección de dependencia que me permita:

  • prueba de unidad de clases sin estado (sin las dependencias)
  • mantenga estas clases sin estado
  • reduce / oculta el código que crea instancias de las dependencias (especialmente si un objeto tiene múltiples dependencias)

Nota: también estoy realizando pruebas de integración, que comprueban las dependencias reales entre objetos.

    
pregunta Class Skeleton 27.05.2016 - 12:57

4 respuestas

4

Si no te gustan los argumentos de constructor adicionales para las dependencias necesita un contenedor DI para manejar la creación de la instancia por usted.

Puede usar un marco de di-container existente o implementar una versión pobre de mansión por su cuenta

public PoorMansDiContainer : IPoorMansDiContainer {
    private IService mService = null;
    private IFooService mFooService = null;

    public IService getSingletonService() {
        if (mService == null) {
            mService = new ServiceImpl(getSingletonFooService());
        }
        return mService;
    }
    public IFooService getSingletonFooService() {
        if (mFooService == null) {
            mFooService = new FooServiceImpl();
        }
        return mFooService;
    }
}
    
respondido por el k3b 27.05.2016 - 14:44
1

Puede configurar sus clases sin estado para referirse directamente a sus dependencias mediante el uso de plantillas. Como ejemplo:

// a dependency interface...
class ILogger
{
public:
    virtual void logSomething (std::string message);
};

// an interface for a service
class IMyService
{
public:
    virtual std::unique_ptr<MyClass> getMyObject () = 0;
};

template <LogProvider>
class MyServiceImpl : public IMyService
{
public:
     virtual std::unique_ptr<MyClass> getMyObject ()
     {
          return LogProvider::logger->logSomething ("creating my object");
          return std::make_unique<MyClass> ();
     }
};

// at global level
struct GetLogger 
{
    static std::shared_ptr<ILogger> logger;
}

// initialisation code...
GetLogger::logger = // ... make a concrete logger here
std::unique_ptr<IMyServce> myService = 
   std::make_unique<MyServiceImpl<GetLogger>> ();

Luego puede crear instancias de MyServiceImpl usando cualquier clase que desee que contenga un registrador. Este enfoque crea datos globales, pero , ya que solo los usa de forma indirecta, no veo ningún problema con ellos .

Aunque, dicho esto, personalmente, simplemente abandonaría la noción de clases de servicio sin estado y, en cambio, cambiaría a clases de servicio inmutables, ya que parece una solución mucho más simple.

    
respondido por el Jules 29.05.2016 - 16:23
0

Utilice un potente localizador de servicios, que es, como Martin Fowler le dirá, casi funcionalmente equivalente a DI. Luego, establezca el servicio en el simulacro que desee durante la prueba en el contexto en el que se ejecuta la prueba. p.ej. (Código C #, lo siento)

public class Service()
{
  public void Foo()
  {
    ...
    IDependency dependency = ServiceLocator.Get<IDependency>();
    ...
  }
}

Para la prueba

ServiceLocator.SetForThisThread<IDependency>(typeof(MockDependency));
RunTest();

Si a alguien le gustaría quejarse de que está creando una dependencia en ServiceLocator, realmente me gustaría entender cómo eso podría ser un problema.

    
respondido por el James Ellis-Jones 28.05.2016 - 13:07
0

He logrado limpiar mis clases sin estado utilizando argumentos predeterminados. Por ejemplo,

virtual unsigned long foo(
    const ISomeInterface & dependency = ProductionDependency()) const override final;

Parece que he cumplido todos los criterios en mi pregunta original ...

unidades sin estado de prueba de unidad (sin las dependencias)

Puedo realizar una prueba unitaria especificando el argumento (por ejemplo, pasar un objeto de prueba falso).

// unit test snippet
Service service;
TestDependency dependency;
const auto actual = service.foo(dependency);

mantenga estas clases sin estado

Las clases sin estado todavía usan solo los constructores predeterminados y no tienen miembros de datos.

reduce / oculta el código que crea una instancia de las dependencias (especialmente si un objeto tiene múltiples dependencias)

Necesitaba hacer los siguientes cambios:

  • Los encabezados de clase base de servicio ahora incluyen los encabezados de dependencia de producción
  • Los argumentos del método ahora incluyen argumentos predeterminados para cada dependencia
  • En algunos casos tuve que cambiar el orden de los argumentos para garantizar que los argumentos predeterminados sean los argumentos finales

Sin embargo, ahora ya no necesito incluir estas dependencias en mi código de cliente. Por ejemplo,

// client code snippet
Service service;
return service.foo();

Esto es lo mismo si mi clase de servicio tiene varias dependencias. Mientras esté usando argumentos predeterminados, puedo usar el mismo fragmento que arriba. Los clientes ya no necesitan saber acerca de las dependencias (o más específicamente, necesitan instanciar los objetos de dependencia porque los obtiene gratis a través de los argumentos predeterminados).

    
respondido por el Class Skeleton 15.06.2016 - 15:49

Lea otras preguntas en las etiquetas