DependencyInjection - Cheiro de injeção excessiva do construtor vs localizador de serviço - onde está a abordagem apropriada?

5

Estou tentando melhorar meus projetos de MVC e li alguns artigos sobre DI, contêineres IoC, Injetores de Construtor e Localizadores de Serviço. Eu queria ir com o Ninject para ajudar com as dependências, mas fiquei intrigado com algumas das coisas que li.

O mais controverso seria o amplamente discutido aqui link (ambas as partes).

E, em seguida, algumas respostas, como resposta de Mark Seemann , e uma follow up

Jeffrey expressa meus medos e os aborda de maneira incorreta - no entanto, não tenho certeza do que fazer no meu caso.

Por exemplo, vamos ver um controlador MVC. Ele serve como uma camada de comunicação entre um aplicativo MCV e um WebAPI. Estou um pouco preocupado com o número de dependências que eu teria ao fazer uma Injeção de Construtor.

public class OcrController : ConnectorControllerBase, IOcrController
{
    private readonly ILogger logger;
    private readonly IConnectorConfigurator config;
    private readonly HttpClient client;
    private readonly IOcrConnector connector;

    public OcrController(
            ILogger logger, 
            IConnectorConfigurator config, 
            HttpClient client, 
            IOcrConnector connector) 
    {
        this.logger = logger;
        this.config = config;
        this.client = client;
        this.connector = connector;
    }
}

Enquanto o IOcrConnector e o HttpClient são depenendências claras com as quais não tenho problemas, estou preocupado com os outros dois. Dependendo de uma ação, é necessária uma configuração ICon para ler algo da configuração e, de maneira semelhante, um Logger pode ser necessário em alguns casos.

O mesmo aconteceria com muitas das minhas classes - uma ou duas dependências reais, o registrador e a configuração e talvez outra coisa no futuro.

No momento, estou usando um ServiceLocator simples e ruim, onde, em vez de passar o ILogger e o IConfig para o construtor, eu os crio quando necessário:

protected ILogger Logger => this.logger 
    ?? (this.logger = ComponentFactory.GetLogger(this.GetType()));

protected IConnectorConfigurator Config => this.config 
    ?? (this.config = ComponentFactory.GetConfig());

O 'Factory' é muito simples, porque ele simplesmente retorna meu wrapper do ILogger como abaixo:

public static ILogger GetLogger(Type type)
{
    return new Log4NetLogger(type);
}

Essa fábrica mora em uma montagem separada, então parece que ela dissocia o ILogger de concreto dos projetos reais o suficiente ...?

Eu não pretendo trocar o ILogger ou o IConfig (que agora é apenas um invólucro para o ConfigurationManager integrado) em tempo de execução ou o que for, mas quando / se necessário, eu apenas alteraria o tipo retornado do GetLogger (Tipo de tipo); método.

Então, por favor, deixe-me saber se esse tipo de combinação de DI apropriado e ServiceLocator faz sentido? Outra alternativa que eu penso seria talvez passar um IComponentFactory para o construtor (em vez de ter um estático globalmente acessado?) Mas isso ainda é um localizador de serviço, apenas colocado em outro lugar ...

Por favor, deixe-me saber o que você pensa

    
por Bartosz 07.05.2017 / 23:53
fonte

3 respostas

4

Em seu exemplo vinculado, o blogueiro argumenta que o código deve ser refatorado porque a dependência "é passada pela cadeia" em vez de ser usada diretamente.

É um cheiro de código 'parâmetro Hobo' em vez de qualquer coisa relacionada à DI. Ele poderia consertá-lo injetando no Order ao invés do OrderProcessor

No seu exemplo, seu controlador presumivelmente utilizará diretamente o ILogger? Então não é o mesmo problema.

Os controladores Moveover MVC sempre exigirão que muitas classes de serviço sejam injetadas, já que elas devem necessariamente ser projetadas de maneira processual.

Considere que uma chamada de método em seu controlador que recebe um parâmetro Order deve construir esse objeto a partir dos dados de valor puro na solicitação por meio de ligação.

Como o parâmetro de entrada Order é criado por esse pipeline, você deve injetar a dependência no controlador ou no fichário.

De qualquer forma, o pipeline deve atuar como a configuração de dependência para a chamada exatamente da maneira que o blogger obtiver, porque o objeto Order deve ser criado a partir dos dados recebidos, em vez de ser passado por referência

    
por 08.05.2017 / 04:28
fonte
1

Há também uma diferença entre injetar uma nova instância toda vez ou injetar a mesma instância novamente: a última é muito mais rápida.

Dito isto, no caso do logger, você pode criar uma instância e injetá-la onde quer que seja necessária praticamente em nenhum momento. O mesmo pode ser verdade para o seu mecanismo de configuração.

    
por 08.05.2017 / 12:36
fonte
1

Passar em ILogger parece um pouco exagerado para mim desde que usei o Log4Net ou até mesmo um dos muitos wrappers do logger permite que você obtenha seu logger facilmente através do factory de log padrão:

private static ILog logger = 
    LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

Não vejo como o uso da injeção de dependência realmente resolve tudo para o registro em log. Até mesmo o wrapper de log da Microsoft no .Net Standard permite esse tipo de acesso ao logger sem sacrificar a capacidade de trocar a implementação com a configuração adequada.

O que realmente deixa apenas um: o IConnectorConfigurator . Pessoalmente, eu provavelmente viveria com injeção de construtor até provarmos que é um problema real. Parece o tipo de coisas que podem ser um singleton em seu aplicativo. Nesse caso, a mesma instância seria fornecida a todas as implementações.

Você precisa se perguntar:

  • Estou lidando com um problema mensurável? (isto é, não é capaz de atender aos requisitos de desempenho)
  • A complexidade adicional do problema vale a complexidade da solução proposta?
  • Como isso afetará o tempo de resposta de correção de bugs no meu aplicativo?

Se você tiver uma dúzia ou mais de parâmetros de construtor, provavelmente deverá repensar a divisão de responsabilidades em seus componentes. Neste caso, tenho a sensação de que a solução proposta adiciona mais complexidade e surpresa aos mantenedores do que resolve. Não me entenda mal, há um momento e um lugar para se desviar da simplicidade da injeção de construtor - eu apenas acho que neste caso é provavelmente um exagero.

    
por 08.05.2017 / 18:37
fonte