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;
}
c#
dependency-injection
class-design
sooprise
fuente
fuente
Respuestas:
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
DO
objeto con una lista deD02
objetos 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
DO2
clase realmente proporcionara alguna funcionalidad adicional que laDO
clase necesita, entonces realmente sería una dependencia. En ese caso, la clase dependienteDO
deberí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 laDO
instancia).Inyectar la dependencia de tal manera hace que el
DO
objeto sea más flexible, ya que cada contexto diferente puede proporcionar su propia implementación de la interfaz alDO
objeto. (Piense en pruebas unitarias).fuente
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 deList<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:
Aleta.
fuente
En su ejemplo,
DO
no tiene dependencias funcionales (básicamente porque no hace nada). Tiene una dependencia del tipo concretoDO2
, por lo que es posible que desee introducir una interfaz para abstraerDO2
, de modo que el consumidor pueda implementar su propia implementación concreta de la clase secundaria.Realmente, ¿qué dependencia inyectarías aquí?
fuente
DOPersister
que sepa cómo persistirDO
y dejarlo como un objeto estrictamente solo de datos (mejor en mi opinión). En este último caso,DOPersister
se inyectaría la dependencia de la base de datos.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:
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.
¡Definitivamente no quieres ocultar la construcción en los métodos CRUD!
Una opción alternativa sería construir el DatabaseService a través de un método de clase reemplazable.
Una alternativa final es usar un ServiceLocator de estilo singleton. Aunque no me gusta esta opción, es comprobable por unidad.
fuente