dependency injection - qué - Yendo plátanos con acoplamiento flexible e inyección de dependencia
inyeccion de dependencias spring (4)
Con las últimas incorporaciones a nuestro marco de inyección de dependencias (anotaciones en la primavera), el costo marginal de crear componentes gestionados por DI parece haber alcanzado un nuevo umbral crítico. Si bien anteriormente había una sobrecarga asociada a la primavera (toneladas de XML e indirecciones adicionales), la inyección de dependencia parece haber comenzado donde muchos patrones van; van debajo del capó y "desaparecen".
La consecuencia de esto es que la sobrecarga conceptual asociada con una gran cantidad de componentes se vuelve aceptable. Es discutible que podamos crear un sistema en el que la mayoría de las clases solo expongan un solo método público y construyan todo el sistema simplemente agregando estas piezas como locas. En nuestro caso, se dan algunas cosas; la interfaz de usuario de su aplicación tiene algunos requisitos funcionales que dan forma a los servicios superiores. Y los sistemas de back-end controlan la parte inferior. Pero entre estos dos, todo está en juego.
Nuestra discusión constante es realmente ¿por qué estamos agrupando cosas en clases y cuáles deberían ser los principios ? Un par de cosas son ciertas; el patrón de la fachada está muerto y enterrado. Cualquier servicio que contenga múltiples características no relacionadas también tiende a dividirse. La "característica no relacionada" se interpreta en un sentido extremadamente más estricto que nunca antes.
En nuestro equipo existen dos líneas de pensamiento que prevalecen aquí: las dependencias de implementación restringen la agrupación; cualquier funcionalidad en una sola clase debería ser preferiblemente un cliente de todas las dependencias inyectadas. Somos un proyecto DDD y la otra fracción cree que el dominio restringe la agrupación (CustomerService o Finder Grained CustomerProductService, CustomerOrderService) - el uso normalizado de dependencias inyectadas no es importante.
Entonces, en el universo de DI débilmente acoplado, ¿por qué estamos agrupando la lógica en clases?
edit: duffymo señala que esto puede estar moviéndose hacia un estilo funcional de programación; que trae a colación el tema del estado. Tenemos bastantes objetos "estatales" que representan piezas (pequeñas) del estado de aplicación relevante. Inyectamos esto en cualquier servicio que tenga una necesidad legítima para este estado. (La razón por la que utilizamos objetos "State" en lugar de objetos de dominio regulares es que los construimos en un tiempo no especificado. Veo esto como una solución alternativa o leve para permitir que la primavera administre la creación real de objetos de dominio. Puede haber mejores soluciones aquí).
Entonces, por ejemplo, cualquier servicio que necesite OrderSystemAccessControlState puede simplemente inyectar esto, y el alcance de estos datos no es fácilmente conocido por el consumidor. Parte del estado relacionado con la seguridad se usa generalmente en muchos niveles diferentes, pero es totalmente invisible en los niveles intermedios. Realmente creo que esto viola fundamentalmente con principios funcionales. Incluso tuve dificultades para adaptarme a este concepto desde la perspectiva OO, pero siempre que el estado inyectado sea preciso y esté fuertemente tipeado, entonces la necesidad es legítima, es decir, el caso de uso es el correcto.
Puedo pensar en dos razones.
Mantenibilidad: naturalmente esperarás que algo de lógica vaya de la mano. La lógica que define las operaciones en un servicio externo particular, por ejemplo, una base de datos, probablemente debería agruparse de forma lógica. Puedes hacer esto en un espacio de nombres o una clase.
Estado e identidad: los objetos no solo contienen lógica sino que también mantienen el estado. La lógica que es parte de la interfaz de trabajo con el estado de un objeto particular debe definirse en ese objeto. Los objetos también mantienen identidad, un objeto que modela una entidad en el dominio del problema debe ser un objeto en su software.
Como un nodo lateral: el argumento de estado e identidad es principalmente aplicable a los objetos de dominio. En la mayoría de las soluciones, he utilizado el contenedor IoC principalmente para los servicios relacionados con estos. Por lo general, los objetos de mi dominio se crean y destruyen como parte del flujo del programa y, por lo general, utilizo objetos de fábrica por separado. Las fábricas luego pueden ser inyectadas y manejadas por el contenedor IoC. He tenido cierto éxito al crear fábricas como envoltorios alrededor del contenedor IoC. De esta forma, el contenedor también maneja la vida útil de los objetos del dominio.
Esta es una pregunta muy interesante. Si miro hacia atrás en la forma en que he implementado cosas en el pasado, puedo ver una tendencia hacia interfaces y clases más pequeñas y más granulares. Las cosas ciertamente mejoraron de esta manera. Sin embargo, no creo que la solución óptima tenga una función por clase. Esto significará efectivamente que está utilizando un lenguaje OO como un lenguaje funcional y, aunque los lenguajes funcionales son muy poderosos, hay mucho que decir acerca de la combinación de los dos paradigmas.
Universo puro DI perfecto, creo que el diseño de clases individuales + método es ideal. En realidad, necesitamos equilibrar el costo de lo que lo hace menos factible.
Factores de costo
- Sobrecarga de DI. Hacer girar todos los subyacentes subyacentes y relacionados para un solo método es costoso. Agruparse en una clase nos permite compensar parte de eso.
- Habilidades: DI es nuevo para muchos (yo mismo ESPECIALMENTE) por lo que es difícil entender cómo hacerlo mejor o salir de diseños antiguos / habituales.
- Las aplicaciones de campo marrón que ya las tienen, es más fácil / más barato / más rápido para vivir con ellas y preocuparse por esto en futuras aplicaciones de campo verde
Afortunadamente mi novato (sí, estoy lleno de palabras inventadas) con DI no me ha hecho completamente equivocado con esto.
- ¿Por qué estamos agrupando cosas en clases y cuáles deberían ser los principios?
¿Estás haciendo hincapié en "Agrupación" o "Clases"?
Si nos preguntas por qué estamos agrupando cosas, entonces me gustaría secundar Medelt, "Mantenibilidad", aunque lo expresaría de otra manera, "Para reducir el costo potencial de los efectos dominó".
Considere, por un momento, no el acoplamiento real entre sus componentes (clases, archivos, cualquiera que sea) sino el posible acoplamiento, es decir, la cantidad máxima posible de dependencias de código fuente entre esos componentes.
Hay un teorema que muestra que, dada una cadena de componentes a - b - c - d - e, tal que a depende de b, etc., la probabilidad de que cambiar e cambie la componente c no puede ser mayor que la posibilidad ese cambio e cambiará d. Y en los sistemas de software reales, la probabilidad de que cambiar e afecte c es generalmente menor que la probabilidad de que el cambio de e afecte a d.
Por supuesto, podrías decir que eso es obvio. Pero podemos hacerlo aún más obvio. En este ejemplo, d tiene una dependencia directa en e, y c tiene una dependencia indirecta (vía d) en e. Por lo tanto, podemos decir que, estadísticamente, un sistema formado predominantemente por dependencias directas sufrirá un mayor efecto dominó que un sistema formado predominantemente por dependencias indirectas.
Dado que, en el mundo real, cada efecto dominó cuesta dinero, podemos decir que el costo del efecto dominó de una actualización del sistema formado predominantemente por dependencias directas será mayor que el costo del efecto dominó de una actualización del sistema formado predominantemente por dependencias indirectas.
Ahora, de vuelta a un posible acoplamiento. Es posible mostrar que, dentro de un contexto de encapsulación absoluta (como Java o C #, donde la encapsulación recursiva no se emplea ampliamente) todos los componentes están potencialmente conectados entre sí a través de una dependencia directa o una dependencia indirecta con un único componente intermedio. Estadísticamente, un sistema que minimiza el acoplamiento de potencial directo entre sus componentes minimiza el costo potencial del efecto dominó debido a cualquier actualización.
¿Y cómo logramos esta distinción entre el acoplamiento de potencial directo e indirecto (como si no hubiéramos dejado salir al gato de la bolsa)? Con encapsulamiento.
La encapsulación es la propiedad de que la información contenida en una entidad modelada solo es accesible mediante interacciones en las interfaces admitidas por esa entidad modelada. La información (que puede ser datos o comportamiento) a la que no se puede acceder a través de estas interfaces se llama "Información oculta". Mediante el comportamiento de ocultar información dentro de un componente, garantizamos que solo se puede acceder de forma indirecta (a través de las interfaces) mediante componentes externos.
Esto requiere necesariamente algún tipo de contenedor de agrupación en el que algún tipo de funcionalidad de grano fino pueda ocultarse por información.
Es por eso que estamos, "agrupando" cosas.
En cuanto a por qué estamos usando clases para agrupar cosas:
A) Las clases proporcionan un mecanismo de encapsulación respaldado por el lenguaje.
B) No solo estamos usando clases: también estamos usando espacios de nombres / paquetes para encapsulación.
Saludos,
Ed.
Los principios primordiales de un buen OO Design no se detienen en el acoplamiento flexible, sino también en la alta cohesión , que se ignora en la mayoría de las discusiones.
Alta cohesión
En la programación de computadoras, la cohesión es una medida de cuán fuertemente relacionadas o centradas están las responsabilidades de un solo módulo. Como se aplica a la programación orientada a objetos, si los métodos que sirven a la clase dada tienden a ser similares en muchos aspectos, entonces se dice que la clase tiene una alta cohesión. En un sistema altamente cohesivo, la legibilidad del código y la probabilidad de reutilización se incrementan, mientras que la complejidad se mantiene manejable.
La cohesión disminuye si:
* The functionality embedded in a class, accessed through its methods, have little in common. * Methods carry out many varied activities, often using coarsely-grained or unrelated sets of data.
Las desventajas de la baja cohesión (o "cohesión débil") son:
* Increased difficulty in understanding modules. * Increased difficulty in maintaining a system, because logical changes in the domain affect multiple modules, and because changes in one module require changes in related modules. * Increased difficulty in reusing a module because most applications won’t need the random set of operations provided by a module.
Una cosa que se pierde cuando la gente se vuelve loca con los contenedores IoC es la cohesión se pierde y la trazabilidad de qué y cómo algo se convierte en algo se convierte en una pesadilla para descubrir más adelante en el camino, porque todas las relaciones están oscurecidas por un montón de configuración XML archivos (Spring te estoy mirando) y clases de implementación mal nombradas.