Hay muchas razones por las cuales los globales son malvados en OOP.
Si el número o el tamaño de los objetos que necesitan compartirse es demasiado grande para pasarlo de manera eficiente en los parámetros de la función, generalmente todos recomiendan la inyección de dependencia en lugar de un objeto global.
Sin embargo, en el caso de que casi todos necesiten saber sobre una determinada estructura de datos, ¿por qué la inyección de dependencias es mejor que un objeto global?
Ejemplo (uno simplificado, para mostrar el punto en general, sin profundizar demasiado en una aplicación específica)
Hay una serie de vehículos virtuales que tienen una gran cantidad de propiedades y estados, desde tipo, nombre, color, velocidad, posición, etc. Varios usuarios pueden controlarlos de forma remota y una gran cantidad de eventos (ambos iniciado y automático) puede cambiar muchos de sus estados o propiedades.
La solución ingenua sería hacer un contenedor global de ellos, como
vector<Vehicle> vehicles;
que se puede acceder desde cualquier lugar.
La solución más amigable para OOP sería hacer que el contenedor sea miembro de la clase que maneja el bucle de eventos principal y que se cree una instancia en su constructor. Cada clase que lo necesite, y sea miembro del hilo principal, tendrá acceso al contenedor a través de un puntero en su constructor. Por ejemplo, si llega un mensaje externo a través de una conexión de red, se hará cargo de una clase (una para cada conexión) que maneja el análisis y el analizador tendrá acceso al contenedor a través de un puntero o referencia. Ahora, si el mensaje analizado produce un cambio en un elemento del contenedor, o requiere algunos datos para realizar una acción, se puede manejar sin la necesidad de arrojar miles de variables a través de señales y ranuras (o peor, almacenarlos en el analizador para luego ser recuperado por quien llamó al analizador). Por supuesto, todas las clases que reciben acceso al contenedor a través de la inyección de dependencia, son parte del mismo hilo. Diferentes hilos no accederán directamente a él, pero harán su trabajo y luego enviarán señales al hilo principal, y las ranuras en el hilo principal actualizarán el contenedor.
Sin embargo, si la mayoría de las clases tendrán acceso al contenedor, ¿qué lo hace realmente diferente de un global? Si tantas clases necesitan los datos en el contenedor, ¿no es la "forma de inyección de dependencias" simplemente un global disfrazado?
Una respuesta sería la seguridad de los subprocesos: aunque me preocupo de no abusar del contenedor global, tal vez otro desarrollador en el futuro, bajo la presión de un plazo cercano, utilice el contenedor global en un subproceso diferente, sin ocuparme de todo Los casos de colisión. Sin embargo, incluso en el caso de la inyección de dependencia, se podría dar un puntero a alguien que se ejecuta en otro hilo, lo que lleva a los mismos problemas.
Respuestas:
La inyección de dependencia es lo mejor desde el pan rebanado , mientras que los objetos globales han sido conocidos por décadas como la fuente de todo mal , por lo que esta es una pregunta bastante interesante.
El punto de inyección de dependencia no es simplemente garantizar que cada actor que necesita algún recurso pueda tenerlo, porque obviamente, si hace que todos los recursos sean globales, entonces cada actor tendrá acceso a cada recurso, problema resuelto, ¿verdad?
El punto de inyección de dependencia es:
El hecho de que en su configuración particular todos los actores necesiten acceso a la misma instancia de recurso es irrelevante. Confía en mí, algún día tendrás la necesidad de reconfigurar las cosas para que los actores tengan acceso a diferentes instancias del recurso, y luego te darás cuenta de que te has pintado en una esquina. Algunas respuestas ya han señalado dicha configuración: pruebas .
Otro ejemplo: supongamos que divide su aplicación en cliente-servidor. Todos los actores en el cliente usan el mismo conjunto de recursos centrales en el cliente, y todos los actores en el servidor usan el mismo conjunto de recursos centrales en el servidor. Ahora suponga, un día, que decide crear una versión "independiente" de su aplicación cliente-servidor, donde tanto el cliente como el servidor están empaquetados en un solo ejecutable y se ejecutan en la misma máquina virtual. (O entorno de tiempo de ejecución, según el idioma que elija).
Si usa la inyección de dependencia, puede asegurarse fácilmente de que todos los actores del cliente reciben las instancias de recursos del cliente para trabajar, mientras que todos los actores del servidor reciben las instancias de recursos del servidor.
Si no utiliza la inyección de dependencia, no tiene suerte, ya que solo puede existir una instancia global de cada recurso en una máquina virtual.
Entonces, debe considerar: ¿todos los actores realmente necesitan acceso a ese recurso? ¿De Verdad?
Es posible que haya cometido el error de convertir ese recurso en un objeto divino (por lo que, por supuesto, todos necesitan acceso a él), o tal vez está sobreestimando enormemente la cantidad de actores en su proyecto que realmente necesitan acceso a ese recurso .
Con los globales, cada línea de código fuente en toda su aplicación tiene acceso a cada recurso global. Con la inyección de dependencia, cada instancia de recurso solo es visible para aquellos actores que realmente la necesitan. Si los dos son iguales (los actores que necesitan un recurso en particular comprenden el 100% de las líneas de código fuente en su proyecto), entonces debe haber cometido un error en su diseño. Entonces, tampoco
Refactorice ese gran gran recurso de dios enorme en subsistemas más pequeños, por lo que diferentes actores necesitan acceder a diferentes piezas, pero rara vez un actor necesita todas sus piezas, o
Refactorice a sus actores para que a su vez acepten como parámetros solo los subconjuntos del problema en el que necesitan trabajar, por lo que no tienen que consultar todo el tiempo un gran recurso central enorme.
fuente
Esa es una afirmación dudosa. El enlace se utiliza como prueba se refiere al estado - mutable globales. Son decididamente malvados. Solo lectura global son constantes. Las constantes son relativamente sanas. Quiero decir, no vas a inyectar el valor de pi en todas tus clases, ¿verdad?
No, ellos no.
Si sus funciones / clases tienen demasiadas dependencias, las personas recomiendan detenerse y ver por qué su diseño es tan malo. Tener una gran cantidad de dependencias es una señal de que su clase / función probablemente esté haciendo demasiadas cosas y / o no tenga suficiente abstracción.
Esta es una pesadilla de diseño. Tienes un montón de cosas que se pueden usar / abusar sin ninguna forma de validación, sin limitaciones de concurrencia, es solo un acoplamiento por todas partes.
Sí, tener acoplamiento a datos comunes en todas partes no es muy diferente de un global, y como tal, sigue siendo malo.
Lo bueno es que estás desacoplando a los consumidores de la variable global misma. Esto le proporciona cierta flexibilidad en cómo se crea la instancia. Tampoco te obliga a tener una sola instancia. Puede hacer que se pasen diferentes a sus diferentes consumidores. También puede crear diferentes implementaciones de su interfaz por consumidor si lo necesita.
Y debido al cambio que inevitablemente viene con los requisitos cambiantes del software, un nivel tan básico de flexibilidad es vital .
fuente
foo
s"?Hay tres razones principales que debe considerar.
En resumen: DI no es un objetivo, es solo una herramienta que hace posible alcanzar un objetivo. Si lo usa incorrectamente (como parece que lo hace en su ejemplo), no se acercará más al objetivo, no hay ninguna propiedad mágica de DI que haga que un mal diseño sea bueno.
fuente
Realmente se trata de la capacidad de prueba: normalmente, un objeto global es muy fácil de mantener y fácil de leer / comprender (una vez que sabe que está allí, por supuesto), pero es un elemento permanente y cuando llega a dividir sus clases en pruebas aisladas, descubre que su el objeto global está atascado diciendo "y yo" y, por lo tanto, sus pruebas aisladas requieren que el objeto global se incluya en ellas. No ayuda que la mayoría de los marcos de prueba no admitan fácilmente este escenario.
Entonces sí, sigue siendo un global. La razón fundamental para tenerlo no ha cambiado, solo la forma en que se accede.
En cuanto a la inicialización, esto sigue siendo un problema: aún tiene que establecer la configuración para que sus pruebas puedan ejecutarse, de modo que no obtenga nada allí. Supongo que podría dividir un objeto dependiente grande en muchos más pequeños y pasar el apropiado a las clases que lo necesitan, pero probablemente esté obteniendo aún más complejidad para superar cualquier beneficio.
Independientemente de todo eso, es un ejemplo de la 'cola que menea al perro', la necesidad de probar es determinar cómo está diseñada la base de código (surgen problemas similares con elementos como clases estáticas, por ejemplo, fecha y hora actual).
Personalmente, prefiero pegar todos mis datos globales en un objeto global (o varios) pero proporcionar accesores para obtener los bits que necesitan mis clases. Entonces puedo burlarme de aquellos para devolver los datos que quiero cuando se trata de pruebas sin la complejidad adicional de DI.
fuente
Además de las respuestas que ya tienes,
Una de las características de la programación OOP es mantener solo las propiedades que realmente necesita para un objeto específico,
AHORA SI su objeto tiene demasiadas propiedades: debe dividir su objeto aún más en subobjetos.
Editar
Ya mencioné por qué los objetos globales son malos, le agregaría un ejemplo.
Debe realizar pruebas unitarias en el código GUI de un formulario de Windows, si usa global, deberá crear todos los botones y sus eventos, por ejemplo, para crear un caso de prueba adecuado ...
fuente