¿Cuál es la diferencia principal entre la inyección de dependencia y el localizador de servicios?

Ja, qué disfraz elegante del Localizador de servicios bajo DI. ¡Incluso podría parecer una DI! :-)

Este es el primer comentario de mi publicación anterior, Inyección de dependencias, JavaScript y módulos ES6 . Gracias al colega symbix 'u por este comentario, tk. Fue él quien se convirtió en la causa de la inmersión en las sutilezas de la diferencia entre unos y otros. Debajo del corte mi respuesta a la pregunta en el título.


imagen


(KDPV no tiene mucho sentido y está destinado principalmente a la identificación visual de esta publicación, entre otros)


Para empezar, trataremos con las definiciones ( algunos de los ejemplos de código que proporcionaré en PHP, algunos en JS, porque estos dos idiomas están actualmente en mi equipaje activo, y algunos en cualquier otro, porque robé estos ejemplos de Internet ).


Inyección de dependencia


DI en pocas palabras: en lugar del módulo require / import, se inyecta la dependencia a través del parámetro constructor (o configurador de propiedades). Es decir, detrás de esta gran palabra está la simple "pasar dependencias de clase a través de parámetros de constructor".

Este comentario del colega de risedphantom transmite con bastante precisión la esencia del fenómeno. Para facilitar la comprensión del código por parte del desarrollador, todas las dependencias se describen explícitamente, en forma de parámetros de constructor (generalmente, pero no siempre):


class MainClass { public function __construct($dep1, $dep2, $dep3) {} } 

Eso es todo. DI: se trata de eso. Las dependencias que necesitamos son proporcionadas por alguien allí. ¿Y a dónde los lleva este "alguien allá afuera"? A DI no le importa.


Al analizar el código, es importante comprender qué es exactamente "interno" para él (y qué podemos cambiar de manera segura), y qué viene / va más allá de la responsabilidad de este código. Esto es lo que emociona a DI. ¿Por qué las dependencias pasan principalmente a través del constructor y no en setters? Debido a que es más conveniente para el desarrollador, ve de inmediato todas las dependencias del código analizado en un solo lugar. Estamos acostumbrados a pensar que DI es algo a nivel de clase, pero los parámetros de función / método también son DI:


 function f(dep1, dep2, dep3) {} 

Un constructor es un método tan especial entre todos los demás.


Servicio de localización


Service Locator es un antipatrón bien conocido. Que hace el Proporciona acceso a un objeto a otros objetos. Aquí hay una interfaz típica para dicho localizador:


 interface ServiceLocator { public function addInstance(string $class, Service $service); public function addClass(string $class, array $params); public function has(string $interface): bool; public function get(string $class): Service; } 

Un localizador es un contenedor en el que puede colocar objetos preparados (o establecer las reglas para su creación) y luego acceder a estos objetos. Para un localizador, en general, no importa cómo aparezcan los objetos en él: se crean explícitamente desde el exterior y se colocan en el contenedor o se crean por el contenedor de acuerdo con las reglas dadas.


El localizador de servicios es responsable de almacenar objetos y proporcionarles acceso. Eso es todo.


Contenedor de inyección de dependencia


¿Qué es un contenedor DI? Según el recuento independiente de los contenidos de " Principios, prácticas y patrones de inyección de dependencias ": "una biblioteca de software que proporciona la funcionalidad DI y permite automatizar muchas de las tareas involucradas en la composición de objetos, la intercepción y la gestión de la vida útil. Los contenedores DI también se conocen como inversión de Contenedores de control (IoC). (§3.2.2) "


Es decir, el contenedor DI es el principal responsable de crear objetos, pero solo en el segundo, para su almacenamiento. Es posible que un contenedor DI ni siquiera almacene ningún objeto creado, si la aplicación en sí misma no necesita objetos que sean comunes a toda la aplicación (como una configuración o un registrador).


Aquí, por ejemplo, está la interfaz del contenedor Symfony DI:


 interface ContainerInterface extends PsrContainerInterface { public function set(string $id, ?object $service); public function get($id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE); public function has($id); public function initialized(string $id); public function getParameter(string $name); public function hasParameter(string $name); public function setParameter(string $name, $value); } 

Si es necesario, puede convencerse muy fácilmente de que la interfaz del contenedor DI es muy similar a la interfaz de los Servicios de localización: el mismo get , has y set / add .


¿Por qué es malo el Localizador de servicios?


Pero nada No la plantilla en sí es mala, sino la forma en que a veces se usa. En particular, existe la opinión de que "el Localizador de servicios viola la encapsulación en lenguajes estáticamente tipados, porque este patrón no expresa claramente las condiciones previas ". Y un ejemplo de violación:


 public class OrderProcessor : IOrderProcessor { public void Process(Order order) { var validator = Locator.Resolve<IOrderValidator>(); if (validator.Validate(order)) { var shipper = Locator.Resolve<IOrderShipper>(); shipper.Ship(order); } } } 

Como, eso es tan malo, pero bueno, así:


 public class OrderProcessor : IOrderProcessor { public OrderProcessor(IOrderValidator validator, IOrderShipper shipper) public void Process(Order order) } 

¡Hey, acabamos de IOrderValidator lado los corchetes del momento de crear los objetos con las IOrderShipper IOrderValidator e IOrderShipper ! Es posible que en esta aplicación en otro lugar haya algo como este código:


 var validator = Locator.Resolve<IOrderValidator>(); var shipper = Locator.Resolve<IOrderShipper>(); var processor = new OrderProcessor(validator, shipper); processor.Process(order); 

Raíz de composición


Hablando de la inyección de dependencia, no podemos evitar llegar a un concepto como la raíz de composición (en adelante, lo llamaré el "punto de reunión"). Este es el mismo " alguien " a quien se delegan las responsabilidades para crear dependencias y su implementación.


En el punto de ensamblaje, todas las dependencias se definen explícitamente, los objetos correspondientes se crean e implementan donde están esperando. Aquí las diferencias entre el Localizador de servicios y el contenedor DI son mínimas. Tanto eso como otro permiten crear nuevos objetos, almacenar los objetos creados, extraer objetos almacenados. Incluso me comprometería a afirmar que en este lugar no hay diferencias fundamentales entre ellos.


La diferencia principal


¿Y dónde son más evidentes las diferencias entre el contenedor DI y el contenedor del Localizador de servicios? En cualquier otro lugar, y especialmente con acceso estático al contenedor:


 $obj = Container::getInstance()->get($objId); 

Aqui esta De esta forma, en cualquier lugar (a menos que sea un Punto de ensamblaje, pero el uso del acceso estático es muy dudoso allí), cualquier contenedor se convierte en un antipatrón llamado Localizador de servicios.


Un colega de VolCh respondió breve y sucintamente mi pregunta :


¿Y cómo crees que la verdadera DI difiere de un Localizador de servicios disfrazado de DI?

así:


Acceso al contenedor

De hecho, toda esta publicación es solo un despliegue más detallado de su respuesta aquí.


Uso legal del contenedor.


Por lo tanto, si un Contenedor es un contenedor DI o un contenedor para el Localizador de servicios depende mucho de dónde y cómo lo usemos. Como dije anteriormente, en el Punto de Asamblea, la diferencia entre los tipos de contenedores desaparece. Pero, ¿qué pasamos si el contenedor en sí es una dependencia para cualquier clase?


 class Foo { public function __construct(Container $container){} } 

Nuevamente, todo depende de cómo usemos el contenedor. Aquí hay dos ejemplos, uno de los cuales coincide exactamente con el contenedor del Localizador de servicios y es un antipatrón, mientras que el otro tiene derecho a la vida bajo ciertas condiciones.


 class Foo { private $dep; public function __construct(Container $container) { $this->dep = $container->get('depId'); } } 

 class Foo { private $container; public function __construct(Container $container) { $this->container = $container; } public function initModules($modules) { foreach ($modules as $initClassId) { $init= $this->container->get($initClassId); $init->exec(); } } } 

Resumen


De hecho, todo es algo más complicado, el contenedor DI debería tener más funcionalidad que el Localizador de Servicios, pero la esencia de esta publicación es que incluso el contenedor DI más sofisticado puede usarse fácilmente como un Localizador de Servicios, pero para usar el Localizador de Servicios como contenedor DI, debe intentarlo.


PS
No puedo evitar proporcionar enlaces al blog de programación de Sergei Teplyakov: estos problemas están muy bien cubiertos allí.

Source: https://habr.com/ru/post/465395/


All Articles