Los contenedores del COI rompen los principios de la POO

51

¿Cuál es el propósito de los contenedores de COI? Las razones combinadas pueden simplificarse de la siguiente manera:

Cuando se utilizan los principios de desarrollo OOP / SOLID, la inyección de dependencias se vuelve desordenada. O tiene los puntos de entrada de nivel superior que administran dependencias para múltiples niveles por debajo de sí mismos y pasan dependencias recursivamente a través de la construcción, o tiene un código algo duplicado en los patrones e interfaces de fábrica / constructor que construyen dependencias según las necesite.

No hay una forma OOP / SOLID de realizar esto Y tener un código súper bonito.

Si esa afirmación anterior es verdadera, ¿cómo lo hacen los contenedores IOC? Hasta donde sé, no están empleando alguna técnica desconocida que no se puede hacer con DI manual. Por lo tanto, la única explicación es que los Contenedores de COI rompen los Principios de POO / SÓLIDO mediante el uso de accesos privados de objetos estáticos.

¿Los contenedores IOC rompen los siguientes principios detrás de escena? Esta es la verdadera pregunta ya que entiendo bien, pero tengo la sensación de que alguien más tiene una mejor comprensión:

  1. Control de alcance . El alcance es la razón de casi todas las decisiones que tomo sobre el diseño de mi código. Bloque, Local, Módulo, Estático / Global. El alcance debe ser muy explícito, tanto a nivel de bloque como lo menos posible en Estático. Debería ver declaraciones, instancias y finalizaciones del ciclo de vida. Confío en el lenguaje y el GC para administrar el alcance siempre que sea explícito con él. En mi investigación, descubrí que los contenedores IOC configuran la mayoría o todas las dependencias como estáticas y las controlan a través de alguna implementación de AOP detrás de escena. Entonces nada es transparente.
  2. La encapsulación . ¿Cuál es el propósito de la encapsulación? ¿Por qué debemos mantener miembros privados así? Por razones prácticas, es así que los implementadores de nuestra API no pueden interrumpir la implementación al cambiar el estado (que debe ser administrado por la clase de propietario). Pero también, por razones de seguridad, son las inyecciones las que superan a nuestros estados miembros y evitan la validación y el control de clase. Entonces, cualquier cosa (marcos de trabajo o marcos de COI) que de alguna manera inyecta código antes de compilar para permitir el acceso externo a miembros privados es bastante enorme.
  3. Principio de responsabilidad única . En la superficie, los contenedores de COI parecen hacer las cosas más limpias. Pero imagine cómo lograría lo mismo sin los marcos auxiliares. Tendría constructores con una docena de dependencias que se pasan. Eso no significa que lo cubra con IOC Containers, ¡es algo bueno! Es una señal para refactorizar su código y seguir SRP.
  4. Abierto / Cerrado . Al igual que SRP no es solo de clase (aplico SRP a líneas de responsabilidad única, y mucho menos a métodos). Abierto / Cerrado no es solo una teoría de alto nivel para no alterar el código de una clase. Es una práctica comprender la configuración de su programa y tener control sobre lo que se altera y lo que se extiende. Los contenedores IOC pueden cambiar la forma en que sus clases funcionan completamente en parte porque:

    • a. El código principal no está haciendo la determinación de cambiar las dependencias, sino la configuración del marco.

    • si. El alcance podría modificarse en un momento que no está controlado por los miembros llamantes, sino que está determinado externamente por un marco estático.

Entonces, la configuración de la clase no está realmente cerrada, sino que se altera en función de la configuración de una herramienta de terceros.

La razón por la que esta es una pregunta es porque no soy necesariamente un maestro de todos los contenedores de COI. Y si bien la idea de un contenedor COI es agradable, parecen ser solo una fachada que cubre una implementación deficiente. Pero para lograr un control de alcance externo, acceso de miembros privados y carga diferida, deben continuar muchas cosas cuestionables no triviales. AOP es excelente, pero la forma en que se logra a través de contenedores COI también es cuestionable.

Puedo confiar en C # y .NET GC para hacer lo que espero. Pero no puedo confiar en la misma herramienta de terceros que está alterando mi código compilado para realizar soluciones a cosas que no podría hacer manualmente.

Por ejemplo: Entity Framework y otros ORM crean objetos fuertemente tipados y los asignan a entidades de base de datos, además de proporcionar la funcionalidad básica de placa de caldera para realizar CRUD. Cualquiera podría construir su propio ORM y continuar siguiendo los Principios OOP / SOLID manualmente. Pero esos marcos nos ayudan a no tener que reinventar la rueda cada vez. Mientras que los Contenedores COI, al parecer, nos ayudan a trabajar deliberadamente en torno a los Principios OOP / SOLID y encubrirlo.

Suamere
fuente
13
+1 La correlación entre IOCy Explicitness of codees exactamente con lo que tengo problemas. La DI manual puede convertirse fácilmente en una tarea, pero al menos coloca el flujo de programa explícito autocontenido en un solo lugar: "Obtienes lo que ves, ves lo que obtienes". Si bien los enlaces y las declaraciones de los contenedores IOC pueden convertirse fácilmente en un programa paralelo oculto por sí solo.
Eugene Podskal
66
¿Cómo es esto incluso una pregunta?
Stephen
8
Esto parece más una publicación de blog que una pregunta.
Bart van Ingen Schenau
44
Tengo una categoría interna para las preguntas, "demasiado argumentativa", que no es una razón formal, pero que rechazo y a veces incluso voto para cerrar "no es una pregunta real". Sin embargo, tiene dos aspectos, y esta pregunta solo cumple con el primero. (1) La pregunta establece una tesis y pregunta si es correcta, (2) el interlocutor responde a las respuestas que no están de acuerdo, defendiendo la posición inicial. La respuesta de Matthew contradice algo de lo que dice la pregunta, así que, a menos que el interlocutor rastree por completo, yo personalmente clasifico esto como una pregunta de buena fe, aunque de la forma "Tengo razón, ¿no?"
Steve Jessop
3
@BenAaronson Gracias Ben. Estoy tratando de comunicar lo que he aprendido a través de mi investigación y obtener respuestas sobre la naturaleza de los contenedores de COI. Como resultado de mi investigación, he encontrado que estos principios están rotos. La mejor respuesta a mi pregunta confirmaría o negaría que los Contenedores COI puedan hacer cosas que no podemos hacer manualmente sin romper los Principios OOP / SOLID. Debido a sus excelentes comentarios, he refactorizado mi pregunta con más ejemplos.
Suamere

Respuestas:

35

Revisaré sus puntos numéricamente, pero primero, hay algo de lo que debe tener mucho cuidado: no confunda cómo un consumidor usa una biblioteca con cómo se implementa la biblioteca . Buenos ejemplos de esto son Entity Framework (que usted mismo cita como una buena biblioteca) y el MVC de ASP.NET. Ambos hacen mucho bajo el capó con, por ejemplo, reflejo, lo que no se consideraría un buen diseño si lo difunde a través del código del día a día.

Estas bibliotecas son absolutamente no "transparente" en la forma en que trabajan, o lo que están haciendo detrás de las escenas. Pero eso no va en detrimento, porque admiten buenos principios de programación en sus consumidores . Entonces, cuando hable de una biblioteca como esta, recuerde que, como consumidor de una biblioteca, no es su trabajo preocuparse por su implementación o mantenimiento. Solo debe preocuparse por cómo ayuda u obstaculiza el código que escribe que usa la biblioteca. ¡No mezcles estos conceptos!

Entonces, para pasar por punto:

  1. Inmediatamente llegamos a lo que solo puedo asumir que es un ejemplo de lo anterior. Usted dice que los contenedores IOC configuran la mayoría de las dependencias como estáticas. Bueno, quizás algunos detalles de implementación de cómo funcionan incluyen almacenamiento estático (aunque dado que tienden a tener una instancia de un objeto como Ninject IKernelcomo su almacenamiento principal, dudo incluso eso). ¡Pero esto no es asunto tuyo!

    En realidad, un contenedor de IoC es tan explícito con alcance como la inyección de dependencia del pobre. (Voy a seguir comparando los contenedores de IoC con la DI del pobre porque compararlos con ninguna DI sería injusto y confuso. Y, para ser claros, no estoy usando la "DI del pobre" como peyorativo).

    En la DI del pobre, crearías una dependencia manualmente, luego la inyectarías en la clase que la necesita. En el punto donde construye la dependencia, elegiría qué hacer con ella: almacénela en una variable de ámbito local, una variable de clase, una variable estática, no la almacene en absoluto, lo que sea. Puede pasar la misma instancia a muchas clases, o puede crear una nueva para cada clase. Lo que sea. El punto es que, para ver qué está sucediendo, mire al punto, probablemente cerca de la raíz de la aplicación, donde se crea la dependencia.

    ¿Qué pasa con un contenedor de IoC? Bueno, haces exactamente lo mismo! Una vez más, pasando por la terminología Ninject, nos fijamos en donde la unión está configurado, y encontrar si se dice algo así InTransientScope, InSingletonScopeo lo que sea. En todo caso, esto es potencialmente más claro, porque tiene un código allí mismo que declara su alcance, en lugar de tener que mirar a través de un método para rastrear lo que le sucede a algún objeto (un objeto puede ser explorado en un bloque, pero se usa varias veces en ese sentido). bloquear o solo una vez, por ejemplo). Entonces, tal vez le repele la idea de tener que usar una función en el contenedor de IoC en lugar de una función de lenguaje sin formato para dictar el alcance, pero siempre y cuando confíe en su biblioteca de IoC, ¡lo que debería! No hay ninguna desventaja real en ello. .

  2. Todavía no sé realmente de qué estás hablando aquí. ¿Los contenedores de IoC consideran las propiedades privadas como parte de su implementación interna? No sé por qué lo harían, pero de nuevo, si lo hacen, no es asunto tuyo cómo se implementa la biblioteca que estás utilizando.

    ¿O tal vez exponen una capacidad como inyectar en setters privados? Honestamente, nunca me he encontrado con esto, y tengo dudas sobre si esto es realmente una característica común. Pero incluso si está allí, es un caso simple de una herramienta que puede ser mal utilizada. Recuerde, incluso sin un contenedor de IoC, son solo unas pocas líneas de código Reflection para acceder y modificar una propiedad privada. Es algo que casi nunca debería hacer, pero eso no significa que .NET sea malo para exponer la capacidad. Si alguien usa de manera tan obvia y salvaje una herramienta, es culpa de la persona, no de la herramienta.

  3. El punto final aquí es similar a 2. ¡La gran mayoría de las veces, los contenedores IoC SÍ usan el constructor! La inyección Setter se ofrece para circunstancias muy específicas donde, por razones particulares, la inyección del constructor no se puede usar. Cualquiera que use la inyección de setter todo el tiempo para encubrir cuántas dependencias se están pasando está dejando de lado la herramienta. Eso NO es culpa de la herramienta, es de ellos.

    Ahora, si este fue un error realmente fácil de hacer inocentemente, y uno que los contenedores de IoC fomentan, entonces está bien, tal vez tengas un punto. Sería como hacer público a cada miembro de mi clase y luego culpar a otras personas cuando modifican cosas que no deberían, ¿verdad? Pero cualquiera que use la inyección de setter para encubrir las violaciones de SRP ignora voluntariamente o ignora por completo los principios básicos de diseño. No es razonable echarle la culpa de esto al contenedor de IoC.

    Eso es especialmente cierto porque también es algo que puedes hacer con la misma facilidad con la DI del pobre:

    var myObject 
       = new MyTerriblyLargeObject { DependencyA = new Thing(), DependencyB = new Widget(), Dependency C = new Repository(), ... };
    

    Entonces, realmente, esta preocupación parece completamente ortogonal a si se usa o no un contenedor de IoC.

  4. Cambiar la forma en que sus clases funcionan juntas no es una violación de OCP. Si lo fuera, entonces toda inversión de dependencia alentaría una violación de OCP. Si este fuera el caso, ¡ambos no estarían en el mismo acrónimo SÓLIDO!

    Además, ni los puntos a) ni b) se acercan a tener algo que ver con OCP. Ni siquiera sé cómo responder a estos con respecto a OCP.

    Lo único que puedo adivinar es que piensas que OCP tiene algo que ver con el comportamiento que no se modifica en el tiempo de ejecución, o sobre desde qué parte del código se controla el ciclo de vida de las dependencias. No es. OCP se trata de no tener que modificar el código existente cuando se agregan o cambian requisitos. Se trata de escribir código, no de cómo el código que ya ha escrito está pegado (aunque, por supuesto, el acoplamiento suelto es una parte importante para lograr OCP).

Y una última cosa que dices:

Pero no puedo confiar en la misma herramienta de terceros que está alterando mi código compilado para realizar soluciones a cosas que no podría hacer manualmente.

Si puedes . No hay absolutamente ninguna razón para que piense que estas herramientas, basadas en un gran número de proyectos, son más defectuosas o propensas a un comportamiento inesperado que cualquier otra biblioteca de terceros.

Apéndice

Acabo de notar que tus párrafos de introducción también podrían usar algunas direcciones. Dice sarcásticamente que los contenedores de IoC "no están empleando alguna técnica secreta de la que nunca hemos oído hablar" para evitar códigos desordenados y propensos a la duplicación para construir un gráfico de dependencia. Y tiene razón, lo que están haciendo es abordar estas cosas con las mismas técnicas básicas que siempre hacemos los programadores.

Déjame hablarte a través de un escenario hipotético. Usted, como programador, crea una aplicación grande y, en el punto de entrada, donde está construyendo su gráfico de objetos, nota que tiene un código bastante desordenado. Hay bastantes clases que se usan una y otra vez, y cada vez que crea una de ellas, debe construir toda la cadena de dependencias debajo de ellas. Además, descubre que no tiene ninguna forma expresiva de declarar o controlar el ciclo de vida de las dependencias, excepto con un código personalizado para cada una. Su código no está estructurado y está lleno de repeticiones. Este es el desorden del que hablas en tu párrafo de introducción.

Entonces, primero, comienzas a refactorizar un bit, donde un código repetido está estructurado lo suficiente, lo extraes en métodos auxiliares, y así sucesivamente. Pero entonces empiezas a pensar: ¿es este un problema que tal vez podría abordar en un sentido general , uno que no es específico para este proyecto en particular pero que podría ayudarte en todos tus proyectos futuros?

Entonces, siéntate, piensa y decide que debería haber una clase que pueda resolver las dependencias. Y esboza qué métodos públicos necesitaría:

void Bind(Type interfaceType, Type concreteType, bool singleton);
T Resolve<T>();

Binddice "donde ve un argumento de tipo constructor interfaceType, pase una instancia de concreteType". El singletonparámetro adicional dice si usar la misma instancia de concreteTypecada vez, o siempre hacer una nueva.

Resolvesimplemente intentará construir Tcon cualquier constructor que pueda encontrar cuyos argumentos sean todos los tipos que se han vinculado previamente. También puede llamarse recursivamente para resolver las dependencias hasta el final. Si no puede resolver una instancia porque no todo ha sido vinculado, arroja una excepción.

Puede intentar implementar esto usted mismo, y encontrará que necesita un poco de reflexión y un poco de almacenamiento en caché para los enlaces donde singletones cierto, pero ciertamente nada dramático u horroroso. Y una vez que haya terminado , ¡ voila , tiene el núcleo de su propio contenedor de IoC! ¿Es realmente tan aterrador? La única diferencia real entre esto y Ninject o StructureMap o Castle Windsor o lo que prefiera es que tienen mucha más funcionalidad para cubrir (¡muchos!) Casos de uso en los que esta versión básica no sería suficiente. Pero en el fondo, lo que tienes allí es la esencia de un contenedor de IoC.

Ben Aaronson
fuente
Gracias Ben Honestamente, solo he trabajado con IOC Containers durante aproximadamente un año, y aunque no lo hago, los tutoriales y colegas a mi alrededor realmente se centran en la inyección de propiedades (incluidas las privadas). Es una locura, y trae a colación todos los puntos que hago. Pero incluso si otros se encuentran en esta situación, creo que lo mejor que pueden hacer es aprender más sobre cómo los Contenedores de COI deben trabajar para seguir los principios y señalar estos rompedores de principios en las revisiones de códigos. Sin embargo ... (continuación)
Suamere
Mi mayor preocupación no se enumera necesariamente en los Principios de POO / SOLIDO, y esa es la explicidad del alcance. Todavía estamos lanzando Block, Local, Module y Global level scope por la ventana para una palabra clave. Toda la idea de usar bloques y declaraciones que determinan cuándo algo cae fuera de alcance o se desecha es enorme para mí, y creo que esa fue la parte más aterradora de IOC Containers para mí, para reemplazar esa pieza básica de desarrollo con una palabra clave de extensión .
Suamere
Así que marqué su respuesta como la respuesta porque realmente aborda el corazón de esto. Y la forma en que lo leo es que solo tienes que confiar en lo que los Contenedores de COI hacen debajo de las cubiertas porque todos los demás lo hacen. Y en lo que respecta a las posibilidades de uso indebido y la forma elegante en que lo cubre, es una preocupación de la tienda y las revisiones de códigos y deben hacer su debida diligencia para seguir los buenos principios de desarrollo.
Suamere
2
En lo que respecta a 4, la inyección de dependencia no está en el acrónimo SÓLIDO . 'D' = 'Principio de inversión de dependencia', que es un concepto totalmente ajeno.
astreltsov
@astr Buen punto. He reemplazado "inyección" por "inversión" ya que creo que eso es lo que quise escribir en primer lugar. Eso sigue siendo una simplificación excesiva, ya que la inversión de dependencia no implica necesariamente implementaciones concretas múltiples, pero creo que el significado es lo suficientemente claro. Sería peligroso depender de una abstracción polimórfica si cambiar las implementaciones fuera una violación de OCP.
Ben Aaronson
27

¿Los envases de COI no rompen muchos de estos principios?

¿En teoria? No. Los contenedores de IoC son solo una herramienta. Puede tener objetos que tienen una única responsabilidad, interfaces bien separadas, no se puede modificar su comportamiento una vez escrito, etc.

¿En la práctica? Absolutamente.

Quiero decir, he escuchado que no pocos programadores dicen que usan contenedores IoC específicamente para permitirle usar alguna configuración para cambiar el comportamiento del sistema, violando el Principio de Abierto / Cerrado. Solía ​​haber bromas sobre la codificación en XML porque el 90% de su trabajo consistía en arreglar la configuración de Spring. Y ciertamente tiene razón en que ocultar las dependencias (eso es cierto, no todos los contenedores de IoC lo hacen) es una forma rápida y fácil de crear código altamente acoplado y muy frágil.

Miremos de una manera diferente. Consideremos una clase muy básica que tiene una única dependencia básica. Depende de un Action, un delegado o un functor que no toma parámetros y devuelve void. ¿Cuál es la responsabilidad de esta clase? No puedes decirlo. Podría nombrar a esa dependencia algo claro como CleanUpActionlo que te hace pensar que hace lo que quieres que haga. Tan pronto como IoC entra en juego, se convierte en algo . Cualquiera puede entrar en la configuración y modificar su software para hacer cosas bastante arbitrarias.

"¿En qué se diferencia eso de pasar un Actionmensaje al constructor?" usted pregunta. Es diferente porque no puede cambiar lo que se pasa al constructor una vez que se implementa el software (¡o en tiempo de ejecución!). Es diferente porque sus pruebas unitarias (o las pruebas unitarias de sus consumidores) han cubierto los escenarios en los que el delegado se pasa al constructor.

"¡Pero ese es un ejemplo falso! ¡Mis cosas no permiten cambiar el comportamiento!" exclamas Lamento decírtelo, pero lo hace. A menos que la interfaz que está inyectando sean solo datos. Y su interfaz no son solo datos, porque si solo fueran datos, estaría construyendo un objeto simple y usándolo. Tan pronto como tenga un método virtual en su punto de inyección, el contrato de su clase con IoC es "Puedo hacer cualquier cosa ".

"¿En qué se diferencia eso del uso de secuencias de comandos para impulsar las cosas? Eso está perfectamente bien". Algunos de ustedes preguntan. No es diferente Desde la perspectiva del diseño del programa, no hay diferencia. Estás llamando a tu programa para ejecutar código arbitrario. Y claro, eso se ha hecho en juegos por años. Se realiza en toneladas de herramientas de administración de sistemas. ¿Es POO? Realmente no.

No hay excusa para su ubicuidad actual. Los contenedores de IoC tienen un propósito sólido: unir sus dependencias cuando tiene tantas combinaciones de ellas, o cambian tan rápidamente que no es práctico hacer explícitas esas dependencias. Sin embargo, muy pocas empresas realmente se ajustan a ese escenario.

Telastyn
fuente
3
Derecha. Y muchas tiendas afirman que su producto es tan complejo que entran en esa categoría, cuando realmente no lo hacen. Como es la verdad con muchos escenarios.
Suamere
12
Pero IoC es exactamente lo contrario de lo que dijiste. Emplea Open / Closeness del programa. Si puede cambiar el comportamiento de todo el programa sin tener que cambiar el código en sí.
Eufórico
66
@Euphoric: abierto para extensión, cerrado para modificación. Si puede cambiar el comportamiento de todo el programa, no está cerrado para modificación, ¿verdad? La configuración de IoC sigue siendo su software, sigue siendo el código utilizado por sus clases para hacer el trabajo, incluso si está en XML en lugar de Java o C # o lo que sea.
Telastyn
44
@Telastyn El "cerrado por modificación" significa que no debería ser necesario cambiar (modificar) el código, para cambiar el comportamiento de un todo. Con cualquier configuración externa, puede apuntarlo a un nuevo dll y tener el comportamiento de un cambio completo.
Eufórico
12
@Telastyn Eso no es lo que dice el principio abierto / cerrado. De lo contrario, un método que toma parámetros violaría OCP porque podría "modificar" su comportamiento al pasarle parámetros diferentes. Del mismo modo, en su versión de OCP, estaría en conflicto directo con DI, lo que haría bastante extraño que ambos aparezcan en el acrónimo SOLID.
Ben Aaronson
14

¿El uso de un contenedor IoC viola necesariamente los principios OOP / SOLID? No se .

¿Puedes usar los contenedores IoC de tal manera que violen los principios de POO / SOLIDO? .

Los contenedores de IoC son solo marcos para administrar dependencias en su aplicación.

Declaró que las inyecciones perezosas, los establecedores de propiedades y algunas otras características que aún no he sentido la necesidad de usar, con lo que estoy de acuerdo, pueden darle un diseño menos que óptimo. Sin embargo, el uso de estas características específicas se debe a limitaciones en la tecnología en la que está implementando contenedores IoC.

Por ejemplo, ASP.NET WebForms, debe utilizar la inyección de propiedades, ya que no es posible crear una instancia de a Page. Esto es comprensible porque WebForms nunca fue diseñado con paradigmas estrictos de OOP en mente.

ASP.NET MVC, por otro lado, es completamente posible usar un contenedor IoC usando la inyección de constructor amigable OOP / SOLID.

En este escenario (usando la inyección del constructor), creo que promueve principios SÓLIDOS por las siguientes razones:

  • Principio de responsabilidad única: tener muchas clases más pequeñas permite que estas clases sean muy específicas y tengan una razón para existir. El uso de un contenedor de IoC hace que organizarlos sea mucho más fácil.

  • Abierto / Cerrado: aquí es donde el uso de un contenedor de IoC realmente brilla, puede extender la funcionalidad de una clase mediante la inyección de diferentes dependencias. Tener dependencias que inyecta le permite modificar el comportamiento de esa clase. Un buen ejemplo es cambiar la clase que está inyectando escribiendo un decorador para decir agregar almacenamiento en caché o registro. Puede cambiar completamente las implementaciones de sus dependencias si está escrito en un nivel apropiado de abstracción.

  • Principio de sustitución de Liskov: no realmente relevante

  • Principio de segregación de interfaz: si lo desea, puede asignar múltiples interfaces a un solo tiempo concreto, cualquier contenedor de IoC que valga un grano de sal lo hará fácilmente.

  • Inversión de dependencia: los contenedores IoC le permiten realizar inversiones de dependencia fácilmente.

Define un montón de dependencias y declara las relaciones y los ámbitos, y bing bang boom: mágicamente tiene algún complemento que altera su código o IL en algún momento y hace que las cosas funcionen. Eso no es explícito ni transparente.

Es a la vez explícito y transparente, debe decirle a su contenedor IoC cómo conectar las dependencias y cómo administrar la vida útil de los objetos. Esto puede ser por convención, por archivos de configuración o por código escrito en las aplicaciones principales de su sistema.

Estoy de acuerdo en que cuando escribo o leo código usando contenedores IOC, se elimina un código poco elegante.

Esta es la razón por la que OOP / SOLID hace que los sistemas sean manejables. Usar un contenedor de IoC está en línea con ese objetivo.

El escritor de esa clase dice: "¡Si no utilizáramos un Contenedor COI, el Constructor tendría una docena de parámetros! ¡Eso es horrible!"

Una clase con 12 dependencias es probablemente una violación de SRP, sin importar en qué contexto la esté usando.

Mateo
fuente
2
Excelente respuesta Además, creo que todos los principales contenedores de COI también admiten AOP, lo que puede ayudar mucho con OCP. Por cuestiones transversales, AOP suele ser una ruta más amigable con OCP que un Decorador.
Ben Aaronson
2
@BenAaronson Teniendo en cuenta que AOP inyecta código en una clase, no lo llamaría más compatible con OCP. Estás abriendo y cambiando con fuerza una clase en compilación / tiempo de ejecución.
Doval
1
@Doval No veo cómo está inyectando código en una clase. Tenga en cuenta que estoy hablando del estilo Castle DynamicProxy, donde tiene un interceptor, en lugar del estilo de tejido IL. Pero un interceptor es esencialmente un decorador que usa algo de reflexión para que no tenga que acoplarse a una interfaz en particular.
Ben Aaronson
"Inversión de dependencia: los contenedores de IoC le permiten realizar inversiones de dependencia fácilmente", no lo hacen, simplemente están aprovechando algo que es beneficioso, ya sea que tenga IoC o no. Lo mismo con otros principios.
Den
Realmente no entiendo la lógica de la explicación de que algo no está mal basado en el hecho, que es un marco. Los ServiceLocators también se crean como marcos y se consideran malos :).
ipavlu
5

¿No se rompen los contenedores IOC, y permiten que los codificadores se rompan, muchos de los principios OOP / SOLID?

La staticpalabra clave en C # permite a los desarrolladores romper una gran cantidad de principios OOP / SOLID, pero sigue siendo una herramienta válida en las circunstancias correctas.

Los contenedores de IoC permiten un código bien estructurado y reducen la carga cognitiva sobre los desarrolladores. Se puede usar un contenedor de IoC para hacer que seguir los principios de SOLID sea mucho más fácil. Puede usarse mal, seguro, pero si excluimos el uso de cualquier herramienta que pueda usarse mal, tendríamos que abandonar la programación por completo.

Entonces, si bien esta queja plantea algunos puntos válidos sobre el código mal escrito, no es exactamente una pregunta y no es exactamente útil.

Stephen
fuente
Entiendo que IOC Containers entiendo que para gestionar el alcance, la pereza y la accesibilidad, altera el código compilado para hacer cosas que rompen OOP / SOLID, pero oculta esa implementación en un marco agradable. Pero sí, además abre otras puertas por uso incorrecto. La mejor respuesta a mi pregunta sería alguna aclaración sobre cómo se implementan los contenedores de COI que agrega o niega mi propia investigación.
Suamere
2
@Suamere: creo que puede estar utilizando una definición demasiado amplia de COI. El que uso no modifica el código compilado. Hay muchas otras herramientas que no son COI que también modifican el código compilado. Tal vez su título debería ser más como: "Modifica el salto de código compilado ...".
Cerad
@Suamere No tengo tanta experiencia con IoC, pero nunca escuché que IoC cambiara el código compilado. ¿Podría proporcionar información sobre las plataformas / idiomas / marcos de los que habla?
Eufórico
1
"Un contenedor de IoC se puede utilizar para hacer que seguir los principios de SOLID sea mucho más fácil". - ¿Puedes elaborar por favor? Explicar cómo ayuda específicamente con cada principio sería útil. Y, por supuesto, aprovechar algo no es lo mismo que ayudar a que algo suceda (por ejemplo, DI).
Den el
1
@Euphoric No sé sobre los frameworks .net de los que hablaba suamere, pero Spring ciertamente hace modificaciones de código ocultas. El ejemplo más obvio es su soporte para la inyección basada en métodos (por lo que anula un método abstracto en su clase para devolver una dependencia que administra). También hace un uso extensivo de proxies generados automáticamente para ajustar su código para proporcionar comportamientos adicionales (por ejemplo, para administrar automáticamente las transacciones). Sin embargo, mucho de esto no está realmente relacionado con su papel como marco DI.
Julio
3

Veo múltiples errores y malentendidos en su pregunta. La primera es que falta distinción entre inyección de propiedad / campo, inyección de constructor y localizador de servicios. Ha habido mucha discusión (por ejemplo, flamewars) sobre cuál es la forma correcta de hacer las cosas. En su pregunta, parece asumir solo la inyección de propiedades e ignorar la inyección del constructor.

Lo segundo es que asume que los contenedores IoC de alguna manera afectan el diseño de su código. No lo hacen, o al menos, no debería ser necesario cambiar el diseño de su código si usa IoC. Sus problemas con las clases con muchos constructores es el caso de IoC que oculta problemas reales con su código (posiblemente una clase que tiene interrupciones SRP) y las dependencias ocultas son una de las razones por las cuales las personas desalientan el uso de la inyección de propiedades y el localizador de servicios.

No entiendo de dónde viene el problema con el principio abierto-cerrado, así que no voy a comentarlo. Pero le falta una parte de SOLID, que tiene una gran influencia en esto: el principio de Inversión de dependencia. Si sigue DIP, descubrirá que invertir una dependencia introduce una abstracción, cuya implementación debe resolverse cuando se crea una instancia de clase. Con este enfoque, terminará con muchas clases, que requieren que se realicen múltiples interfaces y se proporcionen en la construcción para funcionar correctamente. Si tuviera que proporcionar esas dependencias a mano, tendría que hacer mucho trabajo adicional. Los contenedores de IoC facilitan hacer este trabajo e incluso le permiten cambiarlo sin tener que volver a compilar el programa.

Entonces la respuesta a tu pregunta es no. Los contenedores de IoC no rompen los principios de diseño de OOP. Muy al contrario, resuelven los problemas causados ​​por seguir los principios SÓLIDOS (especialmente el principio de Inversión de dependencia).

Eufórico
fuente
Intenté aplicar IoC (Autofac) a un proyecto no trivial recientemente. Me di cuenta de que tenía que hacer un trabajo similar con construcciones que no son del lenguaje (new () vs API): especificando qué debería resolverse en las interfaces y qué debería resolverse en clases concretas, especificando qué debería ser una instancia y qué debería ser un singleton, asegurándose de que la inyección de propiedad funcione correctamente para aliviar una dependencia circular. Decidió abandonar. Sin embargo, funciona bien con los controladores en ASP MVC.
Den
@Den Su proyecto obviamente no se diseñó teniendo en cuenta la inversión de dependencia. Y en ninguna parte dije que usar IoC es trivial.
Eufórico
En realidad, esa es la cosa: estoy usando la Inversión de dependencia desde el principio. La IoC que afecta el diseño más allá es mi mayor preocupación. Por ejemplo, ¿por qué usaría interfaces en todas partes solo para simplificar IoC? También vea el comentario superior sobre la pregunta.
Den
@Den te técnicamente no "necesitas" usar interfaces. Las interfaces ayudan con la burla para las pruebas unitarias. También puede marcar cosas que necesita para burlarse de lo virtual.
Fred