¿Usar inyección de dependencia para objetos de datos?

11

Estoy aprendiendo sobre la inyección de dependencia y estoy atascado en algo. Inyección de dependencias recomienda enviar clases dependientes a través del constructor, pero me pregunto si esto es necesario para los objetos de datos. Dado que la capacidad de prueba unitaria es uno de los principales beneficios de DI, sería un objeto de datos, que solo almacena datos, y no se somete a prueba ningún procedimiento, lo que convierte a DI en una capa innecesaria de complejidad, o todavía ayuda a mostrar dependencias incluso con objetos de datos?

Class DO{
    DO(){
        DataObject2List = new List<DO2>();
    }

    public string Field1;
    public string Field2;
    public List<DO2> DataObject2List;
}

Class DO2{
    public DateTime Date;
    public double Value;
}
sooprise
fuente
¿Qué quiere decir exactamente con "objeto de datos"? Ese no es un término estándar. ¿Estás hablando de un DTO o te refieres a cualquier clase sin métodos (como una parte particularmente aburrida de un modelo de dominio)? Hay una gran diferencia entre los dos.
Aaronaught
Claro, me refiero solo a una clase sin métodos, una clase que solo almacena datos. Si Data Object no es el término correcto para esto, ¿hay uno o simplemente se llama una clase sin métodos?
sooprise
@sooprise Esta es una buena pregunta. Buen pensamiento.
Matthew Rodatus
@sooprise Quizás mi respuesta esté fuera de lugar. ¿Dónde pondrás los métodos CRUD? ¿En una clase de acceso a datos separada que tomaría los Objetos de datos y los mantendría en una tabla de base de datos? Es decir, DataAccess.Create (<DataObject>)?
Matthew Rodatus
1
@Matthew, este sería un objeto de acceso a datos : si de eso es de lo que habla el OP, entonces eso no está claro en absoluto. Las implementaciones modernas tienden a alejarse de este patrón de todos modos, dependiendo de repositorios y / o unidades de trabajo.
Aaronaught

Respuestas:

7

Me gustaría sugerir una aclaración de algunos de los términos que está utilizando aquí, específicamente "dependencia" e "inyección de dependencia".

Dependencia:

Una "dependencia" es típicamente un objeto complejo que realiza alguna funcionalidad de la que otra clase puede necesitar depender. Algunos ejemplos clásicos serían un registrador o un acceso a la base de datos o algún componente que procese una parte particular de la lógica empresarial.

Un objeto de solo datos como un DTO o un objeto de valor no suele denominarse "dependencia", ya que no realizan alguna función necesaria.

Una vez que lo vea de esta manera, lo que está haciendo en su ejemplo ( componer el DOobjeto con una lista de D02objetos a través del constructor) no debe considerarse en absoluto "inyección de dependencia". Es solo establecer una propiedad. Depende de usted si lo proporciona en el constructor o de otra manera, pero simplemente pasarlo a través del constructor no lo convierte en inyección de dependencia.

Inyección de dependencia:

Si su DO2clase realmente proporcionara alguna funcionalidad adicional que la DOclase necesita, entonces realmente sería una dependencia. En ese caso, la clase dependiente DOdebería depender de una interfaz (como ILogger o IDataAccessor) y, a su vez, confiar en el código de llamada para proporcionar esa interfaz (en otras palabras, 'inyectarla' en la DOinstancia).

Inyectar la dependencia de tal manera hace que el DOobjeto sea más flexible, ya que cada contexto diferente puede proporcionar su propia implementación de la interfaz al DOobjeto. (Piense en pruebas unitarias).

Eric King
fuente
7

Voy a hacer mi mejor esfuerzo para cortar la confusión en la pregunta.

En primer lugar, "Objeto de datos" no es un término significativo. Si la única característica definitoria de este objeto es que no tiene métodos, entonces no debería existir en absoluto . Un objeto útil sin comportamiento debe caber en al menos una de las siguientes subcategorías:

  • Los objetos de valor o "registros" no tienen identidad alguna. Deben ser tipos de valor , con semántica de copia en referencia, suponiendo que el entorno lo admita. Como se trata de estructuras fijas, un VO solo debe ser un tipo primitivo o una secuencia fija de primitivas. Por lo tanto, un VO no debe tener dependencias o asociaciones; cualquier constructor no predeterminado existiría únicamente con el propósito de inicializar el valor, es decir, porque no puede expresarse como un literal.

  • Los objetos de transferencia de datos a menudo se confunden por error con objetos de valor. DTO hacer tienen identidades, o por lo menos que pueden hacerlo . El único propósito de un DTO es facilitar el flujo de información de un dominio a otro. Nunca tienen "dependencias". Ellos pueden tener asociaciones (es decir, a una matriz o colección) pero la mayoría de la gente prefiere hacerlos plana. Básicamente, son análogos a las filas en la salida de una consulta de base de datos; son objetos transitorios que generalmente necesitan ser persistentes o serializados, y por lo tanto no pueden hacer referencia a ningún tipo abstracto, ya que esto los haría inutilizables.

  • Finalmente, los objetos de acceso a datos proporcionan un contenedor o fachada a una base de datos de algún tipo. Estos obviamente tienen dependencias, dependen de la conexión de la base de datos y / o los componentes de persistencia. Sin embargo, sus dependencias casi siempre se gestionan externamente y son totalmente invisibles para las personas que llaman. En el patrón Active Record , es el marco el que maneja todo a través de la configuración; en los modelos DAO más antiguos (antiguos según los estándares actuales), solo podría construirlos a través del contenedor. Si viera uno de estos con inyección de constructor, estaría muy, muy preocupado.

También puede estar pensando en un objeto de entidad o "objeto de negocio" , y en este caso usted no desea la inyección de dependencia de apoyo, pero no en la forma en que usted piensa o por las razones que usted piensa. No es para beneficio del código de usuario , es para beneficio de un administrador de entidad u ORM, que silenciosamente inyectará un proxy que intercepta para hacer cosas elegantes como la comprensión de consultas o la carga diferida.

En estos, generalmente no proporciona un constructor para inyección; en su lugar, solo necesita hacer que la propiedad sea virtual y usar un tipo abstracto (por ejemplo, en IList<T>lugar de List<T>). El resto sucede detrás de escena, y nadie es más sabio.

Entonces, en general, diría que un patrón DI visible que se aplica a un "objeto de datos" es innecesario y probablemente incluso una bandera roja; pero en gran parte, eso se debe a que la existencia misma del objeto es una señal de alerta, excepto en el caso de que se utilice específicamente para representar datos de una base de datos. En casi todos los demás casos, es un olor a código, generalmente el comienzo de un modelo de dominio anémico o, al menos, un Poltergeist .

Reiterar:

  1. No cree "objetos de datos".
  2. Si debe crear un "objeto de datos", asegúrese de que tenga un propósito claramente definido . Ese propósito le dirá si DI es apropiado o no. Es imposible tomar decisiones de diseño significativas sobre un objeto que no debería existir en primer lugar.

Aleta.

Aaronaught
fuente
0

En su ejemplo, DOno tiene dependencias funcionales (básicamente porque no hace nada). Tiene una dependencia del tipo concreto DO2, por lo que es posible que desee introducir una interfaz para abstraer DO2, de modo que el consumidor pueda implementar su propia implementación concreta de la clase secundaria.

Realmente, ¿qué dependencia inyectarías aquí?

Scott Whitlock
fuente
Según otra pregunta suya que respondí, creo que la pregunta se refiere a un Objeto de datos con operaciones CRUD incorporadas. ¿Cómo / dónde se inyecta la dependencia de la base de datos en el objeto de datos? En los métodos? En el constructor? De otra manera? La suposición es que la dependencia no debe ocultarse en el cuerpo de los métodos, lo que deshabilita la separación de la dependencia de la base de datos del Objeto de datos para que el Objeto de datos se pueda probar por unidad.
Matthew Rodatus
@ Matthew Rodatus - Ya veo. Sí, en ese caso, tiene dos opciones: inyectar el servicio de persistencia o crear otra clase llamada DOPersisterque sepa cómo persistir DOy dejarlo como un objeto estrictamente solo de datos (mejor en mi opinión). En este último caso, DOPersisterse inyectaría la dependencia de la base de datos.
Scott Whitlock
Después de releer su pregunta, estoy menos seguro. Mi análisis puede estar equivocado. Él dijo en su pregunta que su DO no tendría ningún procedimiento. Eso significaría que la persistencia NO sucede en el DO. En ese caso, su respuesta es correcta: no hay dependencias para inyectar.
Matthew Rodatus
0

Como se trata de un objeto de datos en la capa de acceso a datos, debe depender directamente de un servicio de base de datos. Puede especificar un DatabaseService para el constructor:

DataObject dataObject = new DataObject(new DatabaseService());
dataObject.Update();

Pero, la inyección no tiene que estar en el constructor. Alternativamente, puede proporcionar la dependencia a través de cada método CRUD. Prefiero este método al anterior porque su objeto de datos no necesita saber dónde persistirá hasta que realmente necesite persistirlo.

DataObject dataObject = new DataObject();
dataObject.Update(new DatabaseService());

¡Definitivamente no quieres ocultar la construcción en los métodos CRUD!

public void Update()
{
    // DON'T DO THIS!
    using (DatabaseService dbService = new DatabaseService())
    {
        ...
    }
}

Una opción alternativa sería construir el DatabaseService a través de un método de clase reemplazable.

public void Update()
{
    // GetDatabaseService() is protected virtual, so in unit testing
    // you can subclass the Data Object and return your own
    // MockDatabaseService.
    using (DatabaseService dbService = GetDatabaseService())
    {
        ...
    }
}

Una alternativa final es usar un ServiceLocator de estilo singleton. Aunque no me gusta esta opción, es comprobable por unidad.

public void Update()
{
    // The ServiceLocator would not be a real singleton. It would have a setter
    // property so that unit tests can swap it out with a mock implementation
    // for unit tests.
    using (DatabaseService dbService = ServiceLocator.GetDatabaseService())
    {
        ...
    }
}
Matthew Rodatus
fuente