Ha, que disfarce gracioso do Localizador de Serviço sob DI. Pode até parecer um DI! :-)
Este é o primeiro comentário ao meu post anterior, Injeção de Dependência, JavaScript e Módulos ES6 . Obrigado ao colega symbix 'u por este comentário. foi ele quem se tornou a causa da imersão nas sutilezas da diferença entre um e outro. Sob o corte, minha resposta para a pergunta no título.

(O KDPV não faz muito sentido e se destina principalmente à identificação visual desta publicação, entre outros)
Para começar, trataremos das definições ( alguns dos exemplos de código que fornecerei em PHP, outros em JS, porque essas duas linguagens estão atualmente na minha bagagem ativa e outras em qualquer outra, porque roubei esses exemplos da Internet ).
Injeção de dependência
DI em poucas palavras - em vez do módulo de solicitação / importação, você injeta a dependência por meio do parâmetro do construtor (ou configurador de propriedades). Ou seja, por trás dessa grande palavra está a simples “passar dependências de classe através de parâmetros do construtor”.
Este comentário do colega de risedphantom transmite com bastante precisão a essência do fenômeno. Para facilitar o entendimento do código pelo desenvolvedor, todas as dependências são descritas explicitamente - na forma de parâmetros do construtor (geralmente, mas nem sempre):
class MainClass { public function __construct($dep1, $dep2, $dep3) {} }
Só isso. DI - ele é sobre isso. As dependências de que precisamos são fornecidas por alguém lá. E para onde esse "alguém lá fora" os leva - o DI não se importa.
Ao analisar o código, é importante entender o que exatamente é "interno" para ele (e o que podemos mudar com segurança) e o que vem / vai além da responsabilidade desse código. É isso que excita DI. Por que as dependências são passadas principalmente pelo construtor, e não pelos setters? Por ser mais conveniente para o desenvolvedor, ele vê imediatamente todas as dependências do código analisado em um só lugar. Estamos acostumados a pensar que DI é algo no nível da classe, mas os parâmetros de função / método também são DI:
function f(dep1, dep2, dep3) {}
Um construtor é apenas um método tão especial entre todos os outros.
Localizador de serviço
O Localizador de serviço é um anti-padrão bem conhecido. O que ele faz? Fornece acesso a um objeto para outros objetos. Aqui está uma interface típica para esse 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; }
Um localizador é um contêiner no qual você pode colocar objetos prontos (ou definir as regras para sua criação) e acessar esses objetos. Para um localizador, em geral, não importa como os objetos aparecem nele - eles são explicitamente criados a partir do exterior e colocados no contêiner ou criados pelo próprio contêiner de acordo com as regras especificadas.
O localizador de serviço é responsável por armazenar objetos e fornecer acesso a eles. Só isso.
Recipiente de injeção de dependência
O que é um contêiner DI? De acordo com a recontagem freelance do conteúdo de " Princípios, práticas e padrões de injeção de dependência ": "uma biblioteca de software que fornece funcionalidade de DI e permite automatizar muitas das tarefas envolvidas na composição de objetos, interceptação e gerenciamento de vida útil. Os contêineres de DI também são conhecidos como Inversão de Contêineres de controle (IoC). (§3.2.2) "
Ou seja, o contêiner de DI é o principal responsável pela criação de objetos, mas apenas no segundo - por seu armazenamento. Em um contêiner de DI, nem um único objeto criado pode ser armazenado, se o próprio aplicativo não precisar de objetos comuns a todo o aplicativo (como uma configuração ou criador de logs).
Aqui, por exemplo, está a interface do contêiner do 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); }
Se necessário, você pode facilmente convencer-se de que a interface do contêiner de DI é muito semelhante à interface dos Locator Services - o mesmo get
, has
e set
/ add
.
Por que o localizador de serviço é ruim?
Mas nada. Não é o modelo em si ruim, mas o modo como é usado algumas vezes. Em particular, existe uma opinião de que "o Localizador de Serviço viola o encapsulamento em linguagens estaticamente tipadas, porque esse padrão não expressa claramente as pré-condições ". E um exemplo de violação:
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); } } }
Tipo, isso é tão ruim, mas bom - assim:
public class OrderProcessor : IOrderProcessor { public OrderProcessor(IOrderValidator validator, IOrderShipper shipper) public void Process(Order order) }
Ei, deixamos de IOrderValidator
os colchetes do momento de criar os próprios objetos com as IOrderShipper
e IOrderShipper
! É possível que nesta aplicação em algum outro lugar exista algo como este código:
var validator = Locator.Resolve<IOrderValidator>(); var shipper = Locator.Resolve<IOrderShipper>(); var processor = new OrderProcessor(validator, shipper); processor.Process(order);
Raiz de composição
Falando em Injeção de Dependência, não podemos deixar de chegar a um conceito como o Root de Composição (a seguir chamarei de "Ponto de Montagem"). É o mesmo " alguém " a quem são delegadas as responsabilidades pela criação de dependências e sua implementação.
No ponto de montagem, todas as dependências são definidas explicitamente, os objetos correspondentes são criados e implementados onde estão aguardando. Aqui, as diferenças entre o Localizador de Serviços e o contêiner DI são mínimas. Isso e outro permitem criar novos objetos, armazenar os objetos criados, extrair objetos armazenados. Eu até me comprometeria a afirmar que neste local não há diferenças fundamentais entre eles.
A principal diferença
E onde estão as diferenças entre o contêiner de DI e o contêiner do Localizador de Serviços mais óbvias? Em qualquer outro lugar, e especialmente com acesso estático ao contêiner:
$obj = Container::getInstance()->get($objId);
Aqui está. Nesta forma, em qualquer lugar (a menos que seja um ponto de montagem, mas o uso de acesso estático é muito duvidoso), qualquer contêiner se torna um antipadrão chamado Service Locator.
Um colega do VolCh respondeu rápida e sucintamente à minha pergunta :
E como você acha que o DI verdadeiro difere de um Localizador de Serviço disfarçado de DI?
assim:
Acesso ao contêiner
De fato, toda esta publicação é apenas uma implantação mais detalhada de sua resposta aqui.
Uso legal do contêiner
Portanto, se um contêiner é um contêiner DI ou um contêiner para o Localizador de serviços depende muito de onde e como o usamos. Como eu disse acima, no ponto de montagem, a diferença entre os tipos de contêineres desaparece. Mas o que passamos se o próprio contêiner é uma dependência para qualquer classe?
class Foo { public function __construct(Container $container){} }
Novamente, tudo depende de como usamos o contêiner. Aqui estão dois exemplos, um dos quais corresponde exatamente ao contêiner do Localizador de Serviços e é um antipadrão, enquanto o outro tem direito à vida sob certas condições.
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(); } } }
Sumário
De fato, tudo é um pouco mais complicado, o contêiner de DI deve ter mais funcionalidades que o Localizador de Serviços, mas a essência desta publicação é que mesmo o contêiner de DI mais sofisticado pode ser facilmente usado como Localizador de Serviços, mas para usar o Localizador de Serviços como um contêiner DI - você precisa tentar.
PS
Não posso deixar de fornecer links para o blog de programação de Sergei Teplyakov - essas questões são muito bem abordadas lá.