Práticas de contêiner Injeção de Dependência / IoC ao escrever estruturas


18

Eu usei vários contêineres de IoC (Castle.Windsor, Autofac, MEF, etc) para o .Net em vários projetos. Descobri que eles tendem a ser abusados ​​frequentemente e incentivam várias práticas ruins.

Existem práticas estabelecidas para o uso de contêiner de IoC, principalmente ao fornecer uma plataforma / estrutura? Meu objetivo como escritor de framework é tornar o código o mais simples e fácil de usar possível. Prefiro escrever uma linha de código para construir um objeto do que dez ou até apenas duas.

Por exemplo, alguns códigos cheiram que eu notei e não tenho boas sugestões para:

  1. Grande número de parâmetros (> 5) para construtores. Criar serviços tende a ser complexo; todas as dependências são injetadas pelo construtor - apesar de os componentes raramente serem opcionais (exceto talvez nos testes).

  2. Falta de aulas particulares e internas; Essa pode ser uma limitação específica do uso de C # e Silverlight, mas estou interessado em saber como é resolvido. É difícil dizer o que é uma interface de estruturas se todas as classes forem públicas; Isso me permite acessar partes íntimas que provavelmente não devo tocar.

  3. Acoplar o ciclo de vida do objeto ao contêiner de IoC. Muitas vezes, é difícil construir manualmente as dependências necessárias para criar objetos. O ciclo de vida do objeto é muitas vezes gerenciado pela estrutura de IoC. Já vi projetos nos quais a maioria das aulas é registrada como Singletons. Você tem uma falta de controle explícito e também é forçado a gerenciar os internos (isso se refere ao ponto acima, todas as classes são públicas e você deve injetá-las).

Por exemplo, a estrutura .Net possui muitos métodos estáticos. como DateTime.UtcNow. Muitas vezes eu vi isso envolto e injetado como um parâmetro de construção.

Dependendo da implementação concreta, é difícil testar meu código. Injetar uma dependência dificulta o uso do meu código - principalmente se a classe tiver muitos parâmetros.

Como forneço uma interface testável e fácil de usar? quais são as melhores práticas?


1
Que más práticas eles incentivam? Quanto às aulas particulares, você não precisa ter muitas delas se tiver um bom encapsulamento; por um lado, são muito mais difíceis de testar.
Aaronaught 01/09/12

Err, 1 e 2 são as más práticas. Construtores grandes e não usando encapsulamento para estabelecer uma interface mínima? Além disso, eu disse privado e interno (use internos visíveis para, para testar). Eu nunca usei uma estrutura que me obriga a usar um contêiner IoC específico.
Dave Hillier

2
É possível que talvez um sinal de necessidade de muitos parâmetros para seus construtores de classe seja o cheiro do código e o DIP esteja apenas tornando isso mais óbvio?
dreza 01/09/12

3
Usar um contêiner de IoC específico em uma estrutura geralmente não é uma boa prática. Usar injeção de dependência é uma boa prática. Você está ciente da diferença?
Aaronaught 2/09/12

1
@Aaronaught (de volta dos mortos). O uso de "injeção de dependência é uma boa prática" é falso. Injeção de Dependência é uma ferramenta que deve ser usada apenas quando apropriado. Usar injeção de dependência o tempo todo é uma prática ruim.
precisa

Respostas:


27

O único anti-padrão legítimo de injeção de dependência que eu conheço é o padrão Service Locator , que é um anti-padrão quando uma estrutura DI é usada para ele.

Todos os outros chamados anti-padrões DI que ouvi falar, aqui ou em outros lugares, são apenas casos ligeiramente mais específicos de anti-padrões gerais de design de OO / software. Por exemplo:

  • A injeção excessiva de construtor é uma violação do Princípio de Responsabilidade Única . Muitos argumentos construtores indicam muitas dependências; muitas dependências indica que a classe está tentando fazer muito. Geralmente, esse erro se correlaciona com outros odores de código, como nomes de classe incomumente longos ou ambíguos ("gerenciadores"). As ferramentas de análise estática podem detectar facilmente acoplamento aferente / eferente excessivo.

  • A injeção de dados, em oposição ao comportamento, é um subtipo do antipadrão poltergeist , com o 'geist nesse caso sendo o contêiner. Se uma classe precisa estar ciente da data e hora atuais, você não injeta a DateTime, que são dados; em vez disso, você injeta uma abstração no relógio do sistema (eu costumo chamar o meu ISystemClock, embora eu ache que exista um mais geral no projeto SystemWrappers ). Isso não está correto apenas para DI; é absolutamente essencial para a testabilidade, para que você possa testar funções que variam no tempo sem precisar realmente esperar por elas.

  • Declarar cada ciclo de vida como Singleton é, para mim, um exemplo perfeito de programação de cultos de carga e, em menor grau, o chamado " reservatório de objetos ", coloquialmente denominado . Já vi mais abusos contra os solteiros do que gostaria de lembrar, e muito pouco envolve DI.

  • Outro erro comum são os tipos de interface específicos da implementação (com nomes estranhos como IOracleRepository) feitos apenas para poder registrá-lo no contêiner. Isso é, por si só, uma violação do Princípio da Inversão da Dependência (apenas porque é uma interface, não significa que seja realmente abstrato) e geralmente também inclui o inchaço da interface que viola o Princípio de Segregação da Interface .

  • O último erro que costumo ver é a "dependência opcional", que eles fizeram no NerdDinner . Em outras palavras, existe um construtor que aceita injeção de dependência, mas também outro construtor que usa uma implementação "padrão". Isso também viola o DIP e tende a levar a violações do LSP , pois os desenvolvedores, com o tempo, começam a fazer suposições sobre a implementação padrão e / ou iniciam novas instâncias usando o construtor padrão.

Como diz o velho ditado, você pode escrever FORTRAN em qualquer idioma . Injeção de dependência não é uma bala de prata que vai impedir que os desenvolvedores estragar o seu gerenciamento de dependência, mas faz prevenir uma série de erros comuns / anti-padrões:

...e assim por diante.

Obviamente, você não deseja projetar uma estrutura para depender de uma implementação específica de contêiner de IoC , como Unity ou AutoFac. Ou seja, mais uma vez, violando o DIP. Mas se você pensa em fazer algo assim, já deve ter cometido vários erros de design, porque a Injeção de Dependências é uma técnica de gerenciamento de dependências de uso geral e não está ligada ao conceito de um contêiner de IoC.

Qualquer coisa pode construir uma árvore de dependência; talvez seja um contêiner de IoC, talvez seja um teste de unidade com um monte de zombarias, talvez seja um driver de teste que fornece dados fictícios. Sua estrutura não deve se importar, e a maioria das estruturas que eu vi não se importa, mas elas ainda fazem uso pesado de injeção de dependência, para que possa ser facilmente integrada ao contêiner de IoC de sua escolha.

DI não é ciência de foguetes. Basta tentar evitar newe staticexceto quando há uma razão convincente para usá-los, como um método de utilitário que não tem dependências externas, ou uma classe de utilitário que não poderia ter qualquer finalidade fora do quadro (interoperabilidade invólucros e chaves de dicionário são exemplos comuns de esta).

Muitos dos problemas com as estruturas de IoC surgem quando os desenvolvedores estão aprendendo a usá-los e, em vez de realmente mudar a maneira como lidam com dependências e abstrações para se ajustarem ao modelo de IoC, tente manipular o contêiner de IoC para atender às expectativas de seus clientes. estilo de codificação antigo, que geralmente envolvia alto acoplamento e baixa coesão. Código incorreto é código incorreto, independentemente de usar técnicas de DI ou não.


Eu tenho algumas perguntas. Para uma biblioteca distribuída no NuGet, não posso depender de um sistema de IoC, como é feito pelo aplicativo que usa a biblioteca, não pela própria biblioteca. Em muitos casos, o usuário da biblioteca realmente não se importa com suas dependências internas. Existe um problema em ter um construtor padrão iniciando as dependências internas e um construtor mais longo para teste de unidade e / ou implementações personalizadas? Você também diz para evitar "novo". Isso se aplica a coisas como StringBuilder, DateTime e TimeSpan?
Etienne Charland
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.