php - inyectar - tipos de inyección de dependencia
¿Qué es mejor: Inyección de dependencia+Registro o Inyección de dependencia o Registro global? (2)
En primer lugar, quiero restringir esta pregunta solo al desarrollo web. Por lo tanto, este lenguaje es agnóstico, siempre que el idioma se utilice para el desarrollo web. Personalmente, estoy llegando a esto desde un fondo en PHP.
A menudo necesitamos usar un objeto desde múltiples ámbitos. Por ejemplo, podríamos necesitar usar una clase de base de datos en el ámbito normal pero también de una clase de controlador. Si creamos el objeto de base de datos en el ámbito normal, no podremos acceder a él desde dentro de la clase del controlador. Deseamos evitar la creación de dos objetos de base de datos en diferentes ámbitos y, por lo tanto, necesitamos una forma de reutilizar la clase de base de datos independientemente del ámbito. Para ello, tenemos dos opciones:
- Haga que el objeto de la base de datos sea global para poder acceder a él desde cualquier lugar.
- Pase la clase de base de datos a la clase del controlador en forma de, por ejemplo, un parámetro al constructor del controlador. Esto se conoce como inyección de dependencia (DI).
El problema se vuelve más complejo cuando hay muchas clases involucradas, todos los objetos exigentes en muchos ámbitos diferentes. En ambas soluciones, esto se vuelve problemático porque si hacemos que cada uno de nuestros objetos sea global, estamos poniendo demasiado ruido en el ámbito global y si pasamos demasiados parámetros a una clase, la clase se vuelve mucho más difícil de manejar.
Por lo tanto, en ambos casos, a menudo se ve el uso de un registro. En el caso global, tenemos un objeto de registro que se hace global y luego agregamos todos nuestros objetos y variables para que estén disponibles en cualquier objeto, pero solo colocando una única variable, el registro, en el ámbito global. En el caso de DI, pasamos el objeto de registro a cada clase reduciendo el número de parámetros a 1.
Personalmente, utilizo este último enfoque debido a los muchos artículos que lo defienden sobre el uso de globales, pero he encontrado dos problemas. En primer lugar, la clase de registro contendrá enormes cantidades de recursión. Por ejemplo, la clase de registro contendrá las variables de inicio de sesión de base de datos necesarias para la clase de base de datos. Por lo tanto, necesitamos inyectar la clase de registro en la base de datos. Sin embargo, la base de datos será necesaria para muchas otras clases y, por lo tanto, la base de datos deberá agregarse al registro, se creará un bucle. ¿Pueden los idiomas modernos manejar esto bien o esto está causando grandes problemas de rendimiento? Observe que el registro global no sufre de esto ya que no se transfiere a nada.
En segundo lugar, comenzaré a pasar grandes cantidades de datos a objetos que no los necesitan. A mi base de datos no le importa mi enrutador, pero el enrutador pasará a la base de datos junto con los detalles de conexión de la base de datos. Esto se agrava a través del problema de recursión porque si el enrutador tiene el registro, el registro tiene la base de datos y el registro y el registro se pasa a la base de datos, entonces la base de datos se pasa a sí misma a través del enrutador (es decir, podría hacer $this->registry->router->registry->database
de $this->registry->router->registry->database
desde dentro de la clase de base de datos`).
Además, no veo lo que el DI me está dando aparte de más complejidad. Tengo que pasar una variable extra a cada objeto y debo usar los objetos de registro con $this->registry->object->method()
lugar de $registry->object->method()
. Ahora bien, obviamente esto no es un problema masivo, pero parece innecesario si no me da nada sobre el enfoque global.
Obviamente, estos problemas no existen cuando uso DI sin un registro, pero luego tengo que pasar cada objeto ''manualmente'', lo que resulta en constructores de clase con un número ridículo de parámetros.
Dados estos problemas con ambas versiones de DI, ¿no es un registro global superior? ¿Qué estoy perdiendo al usar un registro global sobre DI?
Una cosa que se menciona a menudo cuando se habla de DI vs Globals es que los globales inhiben su capacidad para probar su programa correctamente. ¿Cómo exactamente me impiden los globales probar un programa en el que DI no lo haría? He leído en muchos lugares que esto se debe al hecho de que un global puede modificarse desde cualquier lugar y, por lo tanto, es difícil burlarse. Sin embargo, me parece que dado que, al menos en PHP, los objetos se pasan por referencia, cambiar un objeto inyectado en alguna clase también lo cambiará en cualquier otra clase en la que se haya inyectado.
Pondré esto como una respuesta ya que me gustaría incluir el código.
He evaluado pasando un objeto en comparación con global
uso global
. Básicamente, creé un objeto relativamente simple, pero uno con una referencia propia y un objeto anidado.
Los resultados:
Passed Completed in 0.19198203086853 Seconds
Globaled Completed in 0.20970106124878 Seconds
Y los resultados son idénticos si elimino el objeto anidado y la referencia propia ...
Entonces sí, parece que no hay una diferencia de rendimiento real entre estos dos métodos diferentes de pasar datos. Así que haz la mejor elección arquitectónica (en mi humilde opinión es la inyección de dependencia) ...
La secuencia de comandos:
$its = 10000;
$bar = new stdclass();
$bar->foo = ''bar'';
$bar->bar = $bar;
$bar->baz = new StdClass();
$bar->baz->ar = ''bart'';
$s = microtime(true);
for ($i=0;$i<$its;$i++) passed($bar);
$e = microtime(true);
echo "Passed Completed in ".($e - $s) ." Seconds/n";
$s = microtime(true);
for ($i=0;$i<$its;$i++) globaled();
$e = microtime(true);
echo "Globaled Completed in ".($e - $s) ." Seconds/n";
function passed($bar) {
is_object($bar);
}
function globaled() {
global $bar;
is_object($bar);
}
Probado en 5.3.2
Vamos a abordar esto uno por uno.
En primer lugar, la clase de registro contendrá enormes cantidades de recursión
No tiene que inyectar la clase de Registry en la clase de base de datos. También puede tener métodos dedicados en el Registro para crear las clases requeridas para usted. O si inyecta el Registro, simplemente no puede almacenarlo, sino que solo puede extraer de él lo que se necesita para que la clase se ejemplifique correctamente. No hay recursión.
Observe que el registro global no sufre de esto ya que no se transfiere a nada.
Puede que no haya recursión para el Registro en sí, pero los objetos en el Registro pueden tener referencias circulares. Esto podría generar potencialmente pérdidas de memoria cuando se desestabilicen objetos del Registro con versiones de PHP anteriores a la 5.3, cuando el recolector de basura no los recolecte correctamente.
En segundo lugar, comenzaré a pasar grandes cantidades de datos a objetos que no los necesitan. A mi base de datos no le importa mi enrutador, pero el enrutador pasará a la base de datos junto con los detalles de conexión de la base de datos.
Cierto. Pero para eso es el Registro. No es muy diferente de pasar $ _GLOBALS a tus objetos. Si no desea eso, no use un Registro, solo pase los argumentos necesarios para que las instancias de la clase estén en un estado válido. O simplemente no lo guarde.
Podría hacer $ this-> registry-> router-> registry-> database
Es poco probable que el enrutador exponga un método público para obtener el Registro. No podrá acceder a la database
de database
desde $this
través del router
, pero podrá acceder a la database
de database
directamente. Ciertamente. Es un registro. Para eso lo escribiste. Si desea almacenar el Registro en sus objetos, puede envolverlos en una Interfaz Segregada que solo permita el acceso a un subconjunto de los datos que contiene.
Obviamente, estos problemas no existen cuando uso DI sin un registro, pero luego tengo que pasar cada objeto ''manualmente'', lo que resulta en constructores de clase con un número ridículo de parámetros.
No necesariamente. Al utilizar la inyección de constructor, puede limitar el número de argumentos a los que sean absolutamente necesarios para poner el objeto en un estado válido. Las dependencias opcionales restantes también se pueden configurar a través de la inyección de setter. Además, nadie le impide agregar los argumentos en un objeto Array o Config. O utilizar Builders .
Dados estos problemas con ambas versiones de DI, ¿no es un registro global superior? ¿Qué estoy perdiendo al usar un registro global sobre DI?
Cuando utiliza un Registro global, está uniendo estrechamente esta dependencia a la clase. Esto significa que las clases de uso ya no se pueden usar sin esta clase de Registro concreta. Usted asume que solo habrá este Registro y no una implementación diferente. Al inyectar los dependecies, usted es libre de inyectar lo que cumpla con la responsabilidad de la dependencia.
Una cosa que se menciona a menudo cuando se habla de DI vs Globals es que los globales inhiben su capacidad para probar su programa correctamente. ¿Cómo exactamente me impiden los globales probar un programa en el que DI no lo haría?
No le impiden probar el código. Simplemente lo hacen más difícil. Cuando Unit-Testing desea tener el sistema en un estado conocido y reproducible. Si su código tiene dependencias en el estado global , debe crear este estado en cada ejecución de prueba.
He leído en muchos lugares que esto se debe al hecho de que un global puede modificarse desde cualquier lugar y por lo tanto es difícil burlarse
Correcto, si una prueba cambia el estado global, podría afectar a las próximas pruebas si no la cambia de nuevo. Esto significa que debe esforzarse para recrear el entorno además de configurar su Subject-Under-Test en un estado conocido. Esto podría ser fácil si solo hay una dependencia, pero si hay muchas y esas dependen también del estado global. Terminarás en Dependency Hell .