Usando las dependencias de la clase de Helper Helper Helper -- # campo con design-patterns campo con dependency-injection camp codereview Relacionados El problema

Using Poor Man's DI to inject helper class dependencies


3
vote

problema

Español

Tuve un método bastante grande en una clase pública que me refactoré en 2 clases de ayuda. Sin embargo, la cosa es que esas 2 clases de ayuda tienen dependencias. Los refactoré en clases auxiliares para poder burlarse y probarlas fácilmente, lo que funcionó perfectamente.

Sin embargo, la cosa es que no quiero tener que registrar mis clases de ayuda en el contenedor de DI, porque conozco a la clase pública siempre utilizará esas implementaciones específicas.

Así es como implementé los constructores de la clase pública:

      /// <summary>     /// Internal constructor used by tests for mocking.     /// </summary>     internal TranslationCompiler(ITranslationCatalogTransformer translationCatalogTransformer, ICompiledCatalogTransformer compiledCatalogTransformer)     {         if (compiledCatalogTransformer == null) throw new ArgumentNullException("compiledCatalogTransformer");         if (translationCatalogTransformer == null) throw new ArgumentNullException("translationCatalogTransformer");         _translationCatalogTransformer = translationCatalogTransformer;         _compiledCatalogTransformer = compiledCatalogTransformer;     }      /// <summary>     /// Public constructor that passes dependencies to concrete implementations of helper classes.     /// </summary>     public TranslationCompiler(IResourceService resourceService, ITranslationSerializer serializer)     {         if (resourceService == null) throw new ArgumentNullException("resourceService");         if (serializer == null) throw new ArgumentNullException("serializer");         _translationCatalogTransformer = new TranslationCatalogTransformer(resourceService);         _compiledCatalogTransformer = new CompiledCatalogTransformer(serializer);     }   

¿Es este un uso aceptable para el DI del hombre pobre? De esta manera, el contenedor DI solo tiene que conocer las dependencias reales para que la clase pública funcione, mientras que sigue siendo muy probable.

Original en ingles

I had a rather large method in one public class that I refactored into 2 helper classes. The thing is though, that those 2 helper classes have dependencies. I refactored them into helper classes so I could mock and test them easily, which worked out perfectly.

However, the thing is I don't want to have to register my helper classes in the DI container, because I know the public class will always be using those specific implementations.

This is how I implemented the public class' constructors:

    /// <summary>     /// Internal constructor used by tests for mocking.     /// </summary>     internal TranslationCompiler(ITranslationCatalogTransformer translationCatalogTransformer, ICompiledCatalogTransformer compiledCatalogTransformer)     {         if (compiledCatalogTransformer == null) throw new ArgumentNullException("compiledCatalogTransformer");         if (translationCatalogTransformer == null) throw new ArgumentNullException("translationCatalogTransformer");         _translationCatalogTransformer = translationCatalogTransformer;         _compiledCatalogTransformer = compiledCatalogTransformer;     }      /// <summary>     /// Public constructor that passes dependencies to concrete implementations of helper classes.     /// </summary>     public TranslationCompiler(IResourceService resourceService, ITranslationSerializer serializer)     {         if (resourceService == null) throw new ArgumentNullException("resourceService");         if (serializer == null) throw new ArgumentNullException("serializer");         _translationCatalogTransformer = new TranslationCatalogTransformer(resourceService);         _compiledCatalogTransformer = new CompiledCatalogTransformer(serializer);     } 

Is this an acceptable use for poor man's DI? This way, the DI container only has to know the actual dependencies for the public class to work, while still being very testable.

        

Lista de respuestas

3
 
vote

No veo nada malo con su implementación: si funciona para usted, y no interfiere con el sistema, ¡vaya por ello!

Incluso iré más y afirmaré que las condiciones de la guardia en su constructor interno son redundantes porque el constructor solo será utilizado por el constructor público único, o por una prueba que:

  1. cuenta con el ayudante, en cuyo caso se producirá un 99887776655544336 y fallará la prueba, o
  2. no necesita al ayudante, en cuyo caso no hay necesidad de burlarse de él ...

Nota final: Supongo que el constructor público en realidad usa resourceService 9988776655544338 En algún código que eliminó de él, de lo contrario, el guardia Condiciones también hay redundantes ...

 

I don't see anything wrong with your implementation - if it works for you, and does not interfere with the system - go for it!

I will even go further and claim that the guard conditions in your internal constructor are redundant because the constructor will only be used by the single public constructor, or by a test which:

  1. Either counts on the helper - in which case a NullPointerException will occur and fail the test, or
  2. Does not need the helper, in which case there is no need to mock it...

Final note: I guess that the public constructor actually uses resourceService and serializer in some code you deleted from it, otherwise, the guard conditions there are also redundant...

 
 
   
   
3
 
vote

par de cosas ...

Primero, tener un código casi idéntico en ambos constructores es un dolor de mantenimiento. Puede estar bien de vez en cuando, pero si lo hace mucho, causará un error un día cuando cambie uno y no el otro.

Podría cambiar el constructor público para llamar al constructor interno como este:

  public TranslationCompiler(IResourceService resourceService, ITranslationSerializer serializer)     : this(new TranslationCatalogTransformer(resourceService), new CompiledCatalogTransformer(serializer)) { }   

Segundo, no es realmente ideal estar creando diferentes constructores (o cualquier otro código) solo para pruebas. Realmente deberías estar burlándose de las interfaces de la API pública. En este caso, el Servicio de IRESURSETRICE y la ITRANSLACIALIZATERIALIZANTER.

Si tiene problemas para burlarse de esas interfaces, es probablemente un indicador de algún otro problema con su diseño. Tal vez esas interfaces se pueden dividir en unas pocas interfaces más pequeñas, o tal vez las pruebas de su unidad estén realizando las suposiciones incorrectas.

Tal vez la publicación una de las pruebas de su unidad ayudaría.

 

Couple of things..

First, having nearly identical code in both constructors is a maintenance pain. It may be okay every so often, but if you do it a lot it will cause a bug one day when you change one and not the other.

You could change the public constructor to call the internal constructor like this:

public TranslationCompiler(IResourceService resourceService, ITranslationSerializer serializer)     : this(new TranslationCatalogTransformer(resourceService), new CompiledCatalogTransformer(serializer)) { } 

Second, it's not really ideal to be creating different constructors (or any other code) just for tests. You really should be mocking the interfaces of the public API instead. In this case the IResourceService and the ITranslationSerializer.

If you are having trouble mocking those interfaces it's probably an indicator of some other issue with your design. Perhaps those interfaces can be broken up into a few smaller interfaces, or maybe your unit tests are making the wrong assumptions.

Maybe posting one of your unit tests would help.

 
 
3
 
vote

Sin embargo, [...] no quiero tener que registrar mis clases de ayuda en el contenedor de DI, porque conozco la clase pública será siempre estar usando esas implementaciones específicas.

Esa es una suposición muy fuerte que está haciendo aquí. ¿Está seguro de que usted nunca quiera inyectar un decorador que lo hará, digamos, informe de la ejecución del informe a la salida de depuración? O uno que atrapa y registros (con notificación por correo electrónico si lo desea) ¡Cualquiera de las excepciones no detectadas que este otro código podría lanzar?

Esto es lo que se le permite hacer, cuando inyectas dependencias como abstracciones - la clase no tiene ni idea del tipo de implementación real , y no 't Care al respecto , porque siempre y cuando el tipo de implementación en cuestión impida la interfaz especificada, cómo se implementa no es una preocupación de esa clase . En realidad, es Ninguno de su negocio .

  /// <summary> /// Public constructor that passes dependencies to concrete implementations of helper classes. /// </summary> public TranslationCompiler(IResourceService resourceService, ITranslationSerializer serializer) {     if (resourceService == null) throw new ArgumentNullException("resourceService");     if (serializer == null) throw new ArgumentNullException("serializer");     _translationCatalogTransformer = new TranslationCatalogTransformer(resourceService);     _compiledCatalogTransformer = new CompiledCatalogTransformer(serializer); }   

La clave para un exitoso Dependencia-inyección implementación, es yendo todo el camino a través de .

No has enumerado tu clase, pero estoy esperando ver esto en algún lugar como justo encima de estos constructores que has listado:

  private readonly ITranslationCatalogTransformer _translationCatalogTransformer; private readonly ICompiledCatalogTransformer _compiledCatalogTransformer;   

Estas son su clase 'dependencias . INVERSION DE CONTROL implica específicamente al regalar ese control que está manteniendo todo por sí mismo, el control sobre los tipos específicos que implementan las abstracciones que depende.

Solo suelta, abraza di en su gloria completa.

Si no hay otras dependencias (¿es usted 9988776655544332 en cualquier otra cosa en cualquier otro lugar?), Luego, al final, este debe ser el único constructor que necesita:

  public TranslationCompiler(ITranslationCatalogTransformer translationCatalogTransformer,                            ICompiledCatalogTransformer compiledCatalogTransformer) {     if (compiledCatalogTransformer == null)          throw new ArgumentNullException("compiledCatalogTransformer");      if (translationCatalogTransformer == null)          throw new ArgumentNullException("translationCatalogTransformer");      _translationCatalogTransformer = translationCatalogTransformer;     _compiledCatalogTransformer = compiledCatalogTransformer; }   

Incluso si usted en realidad termina utilizando una implementación específica para siempre, esa no es una razón para introducir acoplamiento estricto en su arquitectura.


Lo que tienes aquí no es Di . No es di.

Depende de dos clases que cada una tienen sus propias dependencias, que pueda proporcionar a través de un contenedor de IOC: no hay razón para new ellos mismo, a menos que haya falta de contexto en su Post o, más probable, a menos que haya algo que me perdí.

El constructor público que tiene, no toma las dependencias del tipo construido, y esto solo difume las cosas. Di es un reemplazo para un contenedor de IOC, no es una razón para introducir un acoplamiento ajustado.

Usted está inyectando las dependencias de sus dependencias: Esto significa que su código de cliente debe 9988777665544335 las cosas que se crea el objeto no necesita. ¿No huele?

Incluso si usted es que voy a siempre está usando esas implementaciones específicas, por new usted ha descartado todas las ventajas de hacer DI en primer lugar, y ha hecho que sus constructores sean mucho más ambiguos y complejos de lo que deben ser, al menos desde un punto de acceso di, donde se espera que se espera que la firma constructor de Tipo sea qué ese tipo 's dependences son.

... lo que nos lleva a decir, no preguntes.

 

However, [...] I don't want to have to register my helper classes in the DI container, because I know the public class will always be using those specific implementations.

That is a very strong assumption you're making here. Are you sure you'll never want to inject a decorator that will, say, report execution time to the debug output? Or one that catches and logs (with email notification if you want it) whatever uncaught exceptions that this other code could throw?

This is what proper DI allows you to do, when you inject dependencies as abstractions - the class has no clue of the actual implementing type, and doesn't care about it, because as long as the implementing type in question implements the specified interface, how it's implemented is not a concern of that class. It's actually none of its business.

/// <summary> /// Public constructor that passes dependencies to concrete implementations of helper classes. /// </summary> public TranslationCompiler(IResourceService resourceService, ITranslationSerializer serializer) {     if (resourceService == null) throw new ArgumentNullException("resourceService");     if (serializer == null) throw new ArgumentNullException("serializer");     _translationCatalogTransformer = new TranslationCatalogTransformer(resourceService);     _compiledCatalogTransformer = new CompiledCatalogTransformer(serializer); } 

The key to a successful dependency-injection implementation, is going all the way through.

You haven't listed your class, but I'm expecting to see this somewhere like right above these constructors you've listed:

private readonly ITranslationCatalogTransformer _translationCatalogTransformer; private readonly ICompiledCatalogTransformer _compiledCatalogTransformer; 

These are your class' dependencies. Inversion of Control specifically implies giving away that control you're keeping all for yourself - the control over the specific types that implement the abstractions you're depending on.

Just let go, embrace DI in its full glory.

If there aren't any other dependencies (are you newing up anything else anywhere else?), then at the end this should be the only constructor you need:

public TranslationCompiler(ITranslationCatalogTransformer translationCatalogTransformer,                            ICompiledCatalogTransformer compiledCatalogTransformer) {     if (compiledCatalogTransformer == null)          throw new ArgumentNullException("compiledCatalogTransformer");      if (translationCatalogTransformer == null)          throw new ArgumentNullException("translationCatalogTransformer");      _translationCatalogTransformer = translationCatalogTransformer;     _compiledCatalogTransformer = compiledCatalogTransformer; } 

Even if you actually end up using a specific implementation forever, that's not a reason to introduce tight coupling in your architecture.


What you have here isn't Poor Man's DI. It's not DI.

You depend on two classes that each have their own dependencies, that you're able to provide via an IoC container: there's no reason to new them up yourself, unless there's lack of context in your post or, more likely, unless there's something I missed.

The public constructor you have, doesn't take the constructed type's dependencies, and this only uselessly blurs things up. Poor Man's DI is a replacement for an IoC container, not a reason to introduce tight coupling.

You're injecting your dependencies' dependencies: this means your client code must new up things that the object being created doesn't even need. Doesn't it smell?

Even if you are going to always be using those specific implementations, by newing them up yourself you have ruled out all the advantages of doing DI in the first place, and you have made your constructors much more ambiguous and complex than they need to be, at least from a DI standpoint, where a type's constructor signature is expected to tell us what that type's dependencies are.

...which brings us to tell, don't ask.

 
 
 
 

Relacionados problema

1  Uso de la unidad de trabajo DAPPER en la arquitectura de 3 niveles  ( Using dapper unit of work in 3 tier architecture ) 
El Unit of Work : public class UnitOfWork : IUnitOfWork { private IDbConnection _connection; private IDbTransaction _transaction; private bo...

1  Marco de entidades - Código Primer acceso de datos  ( Entity framework code first data access ) 
Estoy buscando sus opiniones sobre las formas de mejorar el acceso a los datos en mi solicitud. Desarrollo aplicaciones web, usando el código de la entidad 6 ...

18  Por el agujero de conejo con mvp  ( Down the rabbit hole with mvp ) 
seguimiento en esta publicación donde implementé un patrón de repositorio en < una clase = "post-tag tag" href = "/ Preguntas / etiquetadas / VBA" rel = "e...

4  Prueba simulada / unidad para este método / clase de servicio irepositivo  ( Mock unit test for this irepositoryservice method class ) 
Tengo el siguiente código: public interface IRepositoryService { /// <summary> /// This will add conditions like Published = true, Deleted = false ...

7  ¿Es esta una forma sensata de usar un contenedor de IOC?  ( Is this a sensible way of using an ioc container ) 
Tengo una clase de fábrica para un sistema de cola que estoy jugando. Clases de consumo solo cuidados que obtienen una interfaz particular (trabajo) como re...

2  Parser de datos de impuestos por carretera  ( Road tax data parser ) 
Estoy haciendo un analizador de datos para los datos de impuestos por carretera (costos) que analiza un archivo JSON con todos los nombres de variables extrañ...

1  Dividir la gran tarea de importación en subtareas más pequeñas  ( Split large import task into smaller subtasks ) 
Tengo una gran tarea de importación que se ejecuta como un cronjob y quiero Para dividir esa tarea en subtareas más pequeñas. Cada subtasco debe ser manejado ...

3  Estructura de Solución MS VS W.R.T. ¿Pruebas de la unidad de inyección de dependencia?  ( Ms vs solution structure w r t dependency injection unit tests ) 
es la siguiente estructura de la solución de MS vs y clases OK W.R.T. Fácilmente Pruebas de unidad con inyección de dependencia ? ¿Se verían tus pruebas un...

6  Dudas sobre la calidad de una API diseñada para su uso con un esfuerzo mínimo  ( Doubts about the quality of an api designed for use with minimal effort ) 
Intentaré proporcionar exactamente la cantidad de contexto que es necesario para comprender la construcción. Construí una API (bueno, una pequeña parte de u...

4  C # interfaz de criptografía  ( C cryptography interface ) 
C # carece de una interfaz común de encriptación y descifrado de estantes para la inyección de dependencia. Casi todas las bases de código que he abierto tien...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos