Estou usando o Unity em C # para injeção de dependência, mas a pergunta deve ser aplicável a qualquer linguagem e estrutura que esteja usando injeção de dependência.
Estou tentando seguir o princípio do SOLID e, portanto, tenho muitas abstrações. Mas agora estou pensando se há uma prática recomendada para quantas injeções uma classe deve injetar?
Por exemplo, eu tenho um repositório com 9 injeções. Será difícil ler para outro desenvolvedor?
As injeções têm as seguintes responsabilidades:
- IDbContextFactory - criando o contexto para o banco de dados.
- IMapper - Mapeamento de entidades para modelos de domínio.
- IClock - Abstracts DateTime.Now para ajudar com testes de unidade.
- IPerformanceFactory - mede o tempo de execução de métodos específicos.
- ILog - Log4net para registro.
- ICollectionWrapperFactory - Cria coleções (que estendem IEnumerable).
- IQueryFilterFactory - gera consultas com base na entrada que consultará o banco de dados.
- IIdentityHelper - Recupera o usuário conectado.
- IFaultFactory - Crie diferentes FaultExceptions (eu uso o WCF).
Não estou realmente desapontado com a forma como delegou as responsabilidades, mas estou começando a ficar preocupado com a legibilidade.
Então, minhas perguntas:
Existe um limite para quantas injeções uma classe deve ter? E se sim, como evitá-lo?
Muitas injeções limitam a legibilidade ou realmente a melhoram?
fonte
Respostas:
Dependências demais podem indicar que a própria classe está fazendo muito. Para determinar se não está fazendo muito:
Olhe para a própria classe. Faria sentido dividi-lo em dois, três, quatro? Faz sentido como um todo?
Veja os tipos de dependências. Quais são específicos do domínio e quais são "globais"? Por exemplo, eu não consideraria
ILog
no mesmo nível queIQueryFilterFactory
: o primeiro estaria disponível na maioria das classes de negócios de qualquer maneira, se eles estiverem usando log. Por outro lado, se você encontrar muitas dependências específicas do domínio, isso pode indicar que a classe está fazendo muito.Veja as dependências que podem ser substituídas por valores.
Isso poderia ser facilmente substituído,
DateTime.Now
sendo passado diretamente para os métodos que exigem saber a hora atual.Observando as dependências reais, não vejo nada que indique coisas ruins acontecendo:
IDbContextFactory - criando o contexto para o banco de dados.
OK, provavelmente estamos dentro de uma camada de negócios em que as classes interagem com a camada de acesso a dados. Parece bem.
IMapper - Mapeamento de entidades para modelos de domínio.
É difícil dizer algo sem a imagem geral. Pode ser que a arquitetura esteja incorreta e o mapeamento seja feito diretamente pela camada de acesso a dados, ou pode ser que a arquitetura esteja perfeitamente correta. Em todos os casos, faz sentido ter essa dependência aqui.
Outra opção seria dividir a classe em duas: uma que lida com o mapeamento e a outra com a lógica de negócios real. Isso criaria uma camada de fato que separará ainda mais o BL do DAL. Se os mapeamentos são complexos, pode ser uma boa ideia. Na maioria dos casos, porém, isso apenas adicionaria complexidade inútil.
IClock - Abstracts DateTime.Now para ajudar com testes de unidade.
Provavelmente não é muito útil ter uma interface (e classe) separada apenas para obter a hora atual. Eu simplesmente passaria
DateTime.Now
para os métodos que exigem a hora atual.Uma classe separada pode fazer sentido se houver outras informações, como fusos horários ou períodos, etc.
IPerformanceFactory - mede o tempo de execução de métodos específicos.
Veja o próximo ponto.
ILog - Log4net para registro.
Essa funcionalidade transcendente deve pertencer à estrutura e as bibliotecas reais devem ser intercambiáveis e configuráveis em tempo de execução (por exemplo, através do app.config no .NET).
Infelizmente, esse não é (ainda) o caso, que permite escolher uma biblioteca e ficar com ela, ou criar uma camada de abstração para poder trocar as bibliotecas mais tarde, se necessário. Se sua intenção é especificamente ser independente da escolha da biblioteca, vá em frente. Se você tiver certeza de que continuará usando a biblioteca por anos, não adicione uma abstração.
Se a biblioteca é muito complexa para usar, um padrão de fachada faz sentido.
ICollectionWrapperFactory - Cria coleções (que estendem IEnumerable).
Eu diria que isso cria estruturas de dados muito específicas que são usadas pela lógica do domínio. Parece uma classe de utilidade. Em vez disso, use uma classe por estrutura de dados com construtores relevantes. Se a lógica de inicialização for um pouco complicada para caber em um construtor, use métodos estáticos de fábrica. Se a lógica for ainda mais complexa, use padrão de fábrica ou de construtor.
IQueryFilterFactory - gera consultas com base na entrada que consultará o banco de dados.
Por que isso não está na camada de acesso a dados? Por que existe um
Filter
no nome?IIdentityHelper - Recupera o usuário conectado.
Não sei por que existe um
Helper
sufixo. Em todos os casos, outros sufixos também não serão particularmente explícitos (IIdentityManager
?)Enfim, faz todo o sentido ter essa dependência aqui.
IFaultFactory - Crie diferentes FaultExceptions (eu uso o WCF).
É a lógica tão complexa que requer o uso de um padrão de fábrica? Por que a injeção de dependência é usada para isso? Você trocaria a criação de exceções entre código de produção e testes? Por quê?
Eu tentaria refatorar isso para simples
throw new FaultException(...)
. Se alguma informação global deve ser adicionada a todas as exceções antes de propagá-las para o cliente, o WCF provavelmente possui um mecanismo no qual você captura uma exceção não tratada e pode alterá-la e repassá-la para o cliente.Medir a qualidade por números geralmente é tão ruim quanto ser pago por linhas de código que você escreve por mês. Você pode ter um alto número de dependências em uma classe bem projetada, pois pode ter uma classe de baixa qualidade usando poucas dependências.
Muitas dependências tornam a lógica mais difícil de seguir. Se a lógica for difícil de seguir, a classe provavelmente está fazendo muito e deve ser dividida.
fonte
Este é um exemplo clássico de DI dizendo que sua classe provavelmente está se tornando grande demais para ser uma única aula. Isso geralmente é interpretado como "uau, o DI faz meus construtores serem maiores que Júpiter, essa técnica é horrível", mas o que realmente diz a você é que "sua classe possui muitas cargas de dependências". Sabendo disso, podemos
Existem inúmeras maneiras de gerenciar dependências e é impossível dizer o que funciona melhor no seu caso sem conhecer seu código e seu aplicativo.
Para responder às suas perguntas finais:
Sim, o limite superior é "demais". Quantos são "muitos"? "Demasiado" é quando a coesão da classe fica "muito baixa". Tudo depende. Normalmente, se sua reação a uma classe é "uau, essa coisa tem muitas dependências", isso é demais.
Eu acho que essa pergunta é enganosa. A resposta pode ser sim ou não. Mas não é a parte mais interessante. O objetivo de injetar dependências é torná-las visíveis. É sobre fazer uma API que não mente. É sobre a prevenção do estado global. É sobre tornar o código testável. É sobre reduzir o acoplamento.
Classes bem projetadas com métodos nomeados e razoavelmente projetados são mais legíveis que as mal projetadas. O DI realmente não melhora nem prejudica a legibilidade por si só, apenas destaca suas opções de design e, se forem ruins, vai arder seus olhos. Isso não significa que o DI tornou seu código menos legível, apenas mostrou que seu código já estava uma bagunça, que você o ocultara.
fonte
De acordo com Steve McConnel, autor do onipresente "Code Complete", a regra geral é que mais de 7 é um cheiro de código que prejudica a manutenção. Pessoalmente, acho que o número é menor na maioria dos casos, mas fazer o DI corretamente resultará em muitas dependências para injetar quando você estiver muito próximo da Raiz da Composição. Isso é normal e esperado. Esse é um dos motivos pelos quais os contêineres de IoC são úteis e valem a complexidade que eles adicionam a um projeto.
Portanto, se você estiver muito próximo do ponto de entrada do seu programa, isso é normal e aceitável. Se você se aprofundar na lógica do programa, provavelmente é um cheiro que deve ser resolvido.
fonte