Estou procurando implementar injeção de dependência em um aplicativo relativamente grande, mas não tenho experiência nele. Estudei o conceito e algumas implementações de IoC e injetores de dependência disponíveis, como Unity e Ninject. No entanto, há uma coisa que está me iludindo. Como devo organizar a criação da instância no meu aplicativo?
O que estou pensando é que posso criar algumas fábricas específicas que conterão a lógica de criar objetos para alguns tipos de classe específicos. Basicamente, uma classe estática com um método que chama o método Ninject Get () de uma instância estática do kernel nesta classe.
Será uma abordagem correta da implementação da injeção de dependência no meu aplicativo ou devo implementá-lo de acordo com outro princípio?
fonte
Respostas:
Não pense ainda sobre a ferramenta que você irá usar. Você pode fazer DI sem um contêiner de IoC.
Primeiro ponto: Mark Seemann tem um livro muito bom sobre DI em .Net
Segundo: raiz da composição. Certifique-se de que toda a configuração seja feita no ponto de entrada do projeto. O restante do seu código deve saber sobre injeções, não sobre qualquer ferramenta que esteja sendo usada.
Terceiro: a injeção de construtor é o caminho mais provável (existem casos em que você não gostaria, mas não muitos).
Quarto: examine o uso de fábricas lambda e outros recursos semelhantes para evitar a criação de interfaces / classes desnecessárias com o único objetivo de injeção.
fonte
Há duas partes em sua pergunta - como implementar o DI corretamente e como refatorar um aplicativo grande para usar o DI.
A primeira parte é bem respondida por @Miyamoto Akira (especialmente a recomendação de ler o livro "injeção de dependência em .net" de Mark Seemann. O blog Marks também é um bom recurso gratuito.
A segunda parte é muito mais complicada.
Um bom primeiro passo seria simplesmente mover toda a instanciação para os construtores de classes - não injetando as dependências, apenas certificando-se de chamar apenas
new
o construtor.Isso destacará todas as violações do SRP que você está cometendo, para que você possa começar a dividir a classe em colaboradores menores.
O próximo problema que você encontrará serão as classes que dependem dos parâmetros de tempo de execução para construção. Geralmente, você pode corrigir isso criando fábricas simples, geralmente
Func<param,type>
inicializando-as no construtor e chamando-as nos métodos.O próximo passo seria criar interfaces para suas dependências e adicionar um segundo construtor às suas classes, exceto essas interfaces. Seu construtor sem parâmetros renovaria as instâncias concretas e as passaria para o novo construtor. Isso é comumente chamado de 'Injeção B * stard' ou 'Pobre homem DI'.
Isso permitirá que você faça alguns testes de unidade e, se esse foi o objetivo principal do refator, pode ser onde você para. O novo código será gravado com a injeção do construtor, mas o código antigo pode continuar funcionando como gravado, mas ainda pode ser testado.
Você pode, é claro, ir além. Se você pretende usar um contêiner IOC, a próxima etapa pode ser substituir todas as chamadas diretas para
new
seus construtores sem parâmetros por chamadas estáticas para o contêiner IOC, essencialmente (ab) usando-o como um localizador de serviço.Isso exibirá mais casos de parâmetros do construtor de tempo de execução para lidar como antes.
Feito isso, você pode começar a remover os construtores sem parâmetros e refatorar para o DI puro.
Em última análise, isso vai dar muito trabalho, portanto, decida por que você quer fazê-lo e priorize as partes da base de código que serão mais beneficiadas pelo refator
fonte
Primeiro, quero mencionar que você está tornando isso muito mais difícil refatorando um projeto existente, em vez de iniciar um novo projeto.
Você disse que é um aplicativo grande, então escolha um pequeno componente para começar. De preferência, um componente 'leaf-node' que não é usado por mais nada. Não sei qual é o estado dos testes automatizados neste aplicativo, mas você estará quebrando todos os testes de unidade desse componente. Então esteja preparado para isso. A etapa 0 é escrever testes de integração para o componente que você modificará se eles já não existirem. Como último recurso (sem infraestrutura de teste; não há necessidade de escrevê-la), descubra uma série de testes manuais que você pode fazer para verificar se esse componente está funcionando.
A maneira mais simples de declarar sua meta para o refator de DI é que você deseja remover todas as instâncias do operador 'novo' desse componente. Eles geralmente se enquadram em duas categorias:
Variável de membro invariável: são variáveis definidas uma vez (geralmente no construtor) e não são reatribuídas para a vida útil do objeto. Para estes, você pode injetar uma instância do objeto no construtor. Você geralmente não é responsável por descartar esses objetos (não quero dizer nunca aqui, mas você realmente não deveria ter essa responsabilidade).
Variável de membro variante / variável de método: são variáveis que serão coletadas como lixo em algum momento durante a vida útil do objeto. Para isso, você deve injetar uma fábrica em sua classe para fornecer essas instâncias. Você é responsável por descartar objetos criados por uma fábrica.
Seu contêiner de IoC (por mais que pareça) será responsável por instanciar esses objetos e implementar suas interfaces de fábrica. O que quer que esteja usando o componente que você modificou precisará saber sobre o contêiner de IoC para que ele possa recuperar seu componente.
Depois de concluir o acima, você poderá colher todos os benefícios que espera obter da DI no componente selecionado. Agora seria um bom momento para adicionar / corrigir esses testes de unidade. Se houver testes de unidade existentes, você terá que decidir se deseja consertá-los injetando objetos reais ou escrever novos testes de unidade usando zombarias.
'Simplesmente' repita o procedimento acima para cada componente do seu aplicativo, movendo a referência para o contêiner de IoC conforme você avança até que apenas as principais necessidades sejam conhecidas.
fonte
A abordagem correta é usar injeção de construtor, se você usar
então você acaba com o localizador de serviço, em vez de injeção de dependência.
fonte
Você diz que deseja usá-lo, mas não indica o porquê.
O DI nada mais é do que fornecer um mecanismo para gerar concreções a partir de interfaces.
Isso por si só vem do DIP . Se o seu código já está escrito nesse estilo e você tem um único local onde as concreções são geradas, o DI não traz mais nada para a festa. A adição de código de estrutura DI aqui seria simplesmente ofuscar e ofuscar sua base de código.
Supondo que você não quiser usá-lo, você normalmente criada a fábrica / Construtor / recipiente (ou qualquer outro) no início do aplicativo para que ele seja claramente visível.
NB, é muito fácil criar o seu próprio, caso deseje, em vez de se comprometer com o Ninject / StructureMap ou o que seja. Se, no entanto, você tiver uma rotatividade razoável de pessoal, poderá lubrificar as rodas para usar uma estrutura reconhecida ou, pelo menos, escrevê-la nesse estilo para que não seja uma curva de aprendizado muito grande.
fonte
Na verdade, o caminho "certo" é NÃO usar uma fábrica, a menos que não exista outra opção (como nos testes de unidade e em certas simulações - para o código de produção, você NÃO usa uma fábrica)! Fazer isso é realmente um antipadrão e deve ser evitado a todo custo. O objetivo principal de um contêiner de DI é permitir que o gadget faça o trabalho por você.
Conforme mencionado acima em uma postagem anterior, você deseja que seu gadget de IoC assuma a responsabilidade pela criação dos vários objetos dependentes no seu aplicativo. Isso significa deixar seu gadget de DI criar e gerenciar as várias instâncias em si. Esse é o ponto principal da DI - seus objetos NUNCA devem saber como criar e / ou gerenciar os objetos dos quais dependem. Fazer o contrário quebra o acoplamento solto.
Converter um aplicativo existente em todas as DIs é um grande passo, mas deixando de lado as óbvias dificuldades em fazê-lo, você também desejará (apenas para facilitar a sua vida) explorar uma ferramenta de DIs que executará automaticamente a maior parte de suas ligações (o núcleo de algo como o Ninject são as
"kernel.Bind<someInterface>().To<someConcreteClass>()"
chamadas que você faz para corresponder suas declarações de interface às classes concretas que deseja usar para implementar essas interfaces. São aquelas chamadas "Vinculadas" que permitem que seu gadget DI intercepte suas chamadas de construtor e forneça o instâncias de objeto dependentes necessárias.Um construtor típico (pseudo-código mostrado aqui) para alguma classe pode ser:Observe que em nenhum lugar desse código havia qualquer código que criava / gerenciava / liberava a instância de SomeConcreteClassA ou SomeOtherConcreteClassB. De fato, nenhuma classe concreta foi sequer referenciada. Então ... onde a mágica aconteceu?
Na parte de inicialização do seu aplicativo, ocorreu o seguinte (novamente, esse é um pseudo-código, mas é bem parecido com o real (Ninject) ...):
Esse pouco de código diz ao dispositivo Ninject para procurar construtores, varrê-los, procurar instâncias de interfaces que foram configuradas para lidar (que são as chamadas "Bind") e depois criar e substituir uma instância da classe concreta em qualquer lugar a instância é referenciada.
Existe uma boa ferramenta que complementa muito bem o Ninject, chamado Ninject.Extensions.Conventions (outro pacote do NuGet), que fará a maior parte deste trabalho para você. Para não tirar a excelente experiência de aprendizado que você experimentará ao criar isso sozinho, mas para começar, isso pode ser uma ferramenta para investigar.
Se a memória servir, o Unity (formalmente da Microsoft agora um projeto de código aberto) possui uma ou duas chamadas de método que fazem a mesma coisa, outras ferramentas têm ajudantes semelhantes.
Qualquer que seja o caminho que você escolher, leia definitivamente o livro de Mark Seemann para a maior parte do seu treinamento em DI, no entanto, deve-se salientar que mesmo os "Grandes" do mundo da engenharia de software (como Mark) podem cometer erros flagrantes - Mark esqueceu tudo. Ninject em seu livro, então aqui está outro recurso escrito apenas para Ninject. Eu tenho isso e é uma boa leitura: Dominando o Ninject para injeção de dependência
fonte
Não existe um "caminho certo", mas existem alguns princípios simples a seguir:
Isso é tudo. Certamente, esses são princípios, não leis, mas se você os seguir, poderá ter certeza de que está fazendo DI (por favor, corrija-me se eu estiver errado).
Então, como criar objetos durante o tempo de execução sem "novo" e sem conhecer o contêiner de DI?
No caso do NInject, há uma extensão de fábrica que fornece a criação de fábricas. Claro, as fábricas criadas ainda têm uma referência interna ao kernel, mas isso não é acessível a partir do seu aplicativo.
fonte