Por que preciso de um contêiner de IoC em vez de um código DI simples? [fechadas]

598

Eu tenho usado injeção de dependência (DI) por um tempo, injetando em um construtor, propriedade ou método. Eu nunca senti a necessidade de usar um contêiner de Inversão de Controle (IoC). No entanto, quanto mais leio, mais pressão sinto da comunidade para usar um contêiner de IoC.

Eu brinquei com contêineres .NET como StructureMap , NInject , Unity e Funq . Ainda não consigo ver como um contêiner de IoC irá beneficiar / melhorar meu código.

Também tenho medo de começar a usar um contêiner no trabalho, porque muitos dos meus colegas de trabalho verão código que eles não entendem. Muitos deles podem estar relutantes em aprender novas tecnologias.

Por favor, me convença de que preciso usar um contêiner de IoC. Vou usar esses argumentos quando conversar com meus colegas desenvolvedores no trabalho.

Vadim
fonte
6
Boa pergunta. Estou respondendo a isso nos comentários, pois sou muito novo na idéia do COI. Parece a idéia de componentes plug and play e acoplamento solto. Se você precisará usar algum outro componente no lugar do atual, é necessário. O uso do COI é preparar o código para essa alteração, se surgir na IMO.
Shahkalpesh 16/05/2009
3
@shahkalpesh mas posso conseguir um acoplamento solto com DI simples. No entanto, entendo seu ponto de vista caso eu use o arquivo de configuração. No entanto, não tenho certeza se gosto de usar o arquivo de configuração. Os arquivos de configuração são muito detalhados, dificuldades com refatoração e alternância entre vários arquivos.
Vadim
110
apenas adicionando um comentário, pois parece que você está procurando razões para usar a IoC principalmente; mas algo mais precisa ser dito sobre o seu problema. Você não pode escrever seu código no nível da pessoa pior da sua equipe ou por medo de que ela não queira aprender. Você tem uma responsabilidade não apenas de ser profissional, mas também de seu futuro crescer e sua equipe não deve impedir você.
23411 Kevin Sheffield
4
@ Kevin, na verdade, estamos usando IoC. Não foi tão difícil quanto se ensinou a todos sobre IoC.
Vadim
21
Não tenho ideia de por que os moderadores fecham perguntas maciçamente aprovadas e úteis. Talvez neste caso, Will não entenda a pergunta ou ele não entenda para que as pessoas usam a troca de pilha? Se "não for adequado para o formato de perguntas e respostas da stackexchange", talvez considere que sua política está errada e nem todas essas centenas de perguntas úteis com centenas de votos positivos e respostas úteis votadas.
NickG

Respostas:

441

Uau, não acredito que Joel seria a favor disso:

var svc = new ShippingService(new ProductLocator(), 
   new PricingService(), new InventoryService(), 
   new TrackingRepository(new ConfigProvider()), 
   new Logger(new EmailLogger(new ConfigProvider())));

por cima disto:

var svc = IoC.Resolve<IShippingService>();

Muitas pessoas não percebem que sua cadeia de dependências pode ficar aninhada e rapidamente se torna difícil ligá-las manualmente. Mesmo com fábricas, a duplicação do seu código simplesmente não vale a pena.

Os contêineres de IoC podem ser complexos, sim. Mas neste caso simples, mostrei que é incrivelmente fácil.


Ok, vamos justificar isso ainda mais. Digamos que você tenha algumas entidades ou objetos de modelo que deseja vincular a uma interface inteligente. Essa interface inteligente (que chamaremos de Shindows Morms) deseja que você implemente INotifyPropertyChanged para que ele possa fazer o rastreamento de alterações e atualizar a interface de acordo.

"OK, isso não parece tão difícil", então você começa a escrever.

Você começa com isso:

public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime CustomerSince { get; set; }
    public string Status { get; set; }
}

..e acabar com isso :

public class UglyCustomer : INotifyPropertyChanged
{
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            string oldValue = _firstName;
            _firstName = value;
            if(oldValue != value)
                OnPropertyChanged("FirstName");
        }
    }

    private string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set
        {
            string oldValue = _lastName;
            _lastName = value;
            if(oldValue != value)
                OnPropertyChanged("LastName");
        }
    }

    private DateTime _customerSince;
    public DateTime CustomerSince
    {
        get { return _customerSince; }
        set
        {
            DateTime oldValue = _customerSince;
            _customerSince = value;
            if(oldValue != value)
                OnPropertyChanged("CustomerSince");
        }
    }

    private string _status;
    public string Status
    {
        get { return _status; }
        set
        {
            string oldValue = _status;
            _status = value;
            if(oldValue != value)
                OnPropertyChanged("Status");
        }
    }

    protected virtual void OnPropertyChanged(string property)
    {
        var propertyChanged = PropertyChanged;

        if(propertyChanged != null)
            propertyChanged(this, new PropertyChangedEventArgs(property));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Esse é um código de canalização nojento, e eu mantenho que, se você está escrevendo um código como esse manualmente, está roubando seu cliente . Há uma maneira melhor e mais inteligente de trabalhar.

Já ouviu esse termo, trabalha de maneira mais inteligente, não mais?

Bem, imagine que um cara inteligente da sua equipe apareceu e disse: "Aqui está uma maneira mais fácil"

Se você tornar suas propriedades virtuais (acalme-se, não é nada demais), poderemos incorporar esse comportamento automaticamente. (Isso se chama AOP, mas não se preocupe com o nome, concentre-se no que ele fará por você)

Dependendo da ferramenta de IoC que você estiver usando, você pode fazer algo parecido com isto:

var bindingFriendlyInstance = IoC.Resolve<Customer>(new NotifyPropertyChangedWrapper());

Poof! Todo esse manual INotifyPropertyChanged BS agora é gerado automaticamente para você, em todos os configuradores de propriedades virtuais do objeto em questão.

Isso é mágico? SIM ! Se você pode confiar no fato de que esse código faz seu trabalho, pode ignorar com segurança toda essa propriedade que envolve o mumbo-jumbo. Você tem problemas de negócios para resolver.

Alguns outros usos interessantes de uma ferramenta de IoC para fazer AOP:

  • Transações de banco de dados declarativas e aninhadas
  • Unidade de trabalho declarativa e aninhada
  • Exploração madeireira
  • Condições pré / pós (Design by Contract)
Ben Scheirman
fonte
28
Opa, você esqueceu de tornar todas as propriedades virtuais e não funcionou. O tempo que você precisou para gastar depurando esse roubo do seu cliente também? (Desculpas se você não está usando DynamicProxy: P.)
Thom
17
Você sacrifica muita clareza usando o wrapper INotifyPropertyChanged. Se você demorar mais do que alguns minutos para escrever esse encanamento, estará no negócio errado. Menos não é mais quando se trata da verbosidade do seu código!
Supervisionado em 7/10/09
39
@overstained - eu discordo completamente. Primeiro, onde a clareza importa neste exemplo? O consumidor mais próximo. O consumidor está solicitando explicitamente INotifyPropertyChanged. Só porque nós pode criar esse tipo de código com a geração de código micro ou macro, não significa que ele é uma boa idéia para escrevê-lo. Esse lance de INotifyPropertyChanged é apenas rotineiro, mundano, encanador e na verdade diminui a legibilidade da classe em questão.
21411 Ben Scheirman
31
Marcado porque você está confundindo o padrão de Injeção de Dependência e o padrão do Localizador de Serviço. O primeiro deve ser usado em vez do último. Por exemplo, um objeto (ou mesmo todo o sistema) nunca deve chamar Resolve (...) para obter uma instância do IShippingService. Os objetos que precisam de um IShippingService devem receber uma referência à instância que devem usar quando construídos, e uma camada superior é responsável por conectar os dois objetos.
Nat
30
Seja o que for (IoC, DynamicProxy, AOP ou magia negra ), alguém pode me vincular a um artigo concreto / documentação específica na estrutura que fornece mais detalhes para conseguir isso?
Fostandy 19/05/10
138

Estou com você, Vadim. Os contêineres de IoC adotam um conceito simples, elegante e útil e transformam-o em algo que você deve estudar por dois dias com um manual de 200 páginas.

Pessoalmente, estou perplexo com a maneira como a comunidade de IoC pegou um artigo bonito e elegante de Martin Fowler e o transformou em várias estruturas complexas, tipicamente com manuais de 200 a 300 páginas.

Tento não julgar (HAHA!), Mas acho que as pessoas que usam contêineres de IoC são (A) muito inteligentes e (B) carecem de empatia por pessoas que não são tão inteligentes quanto são. Tudo faz sentido para eles, portanto eles têm problemas para entender que muitos programadores comuns acharão os conceitos confusos. É a maldição do conhecimento . As pessoas que entendem os contêineres de IoC têm dificuldade em acreditar que existem pessoas que não entendem isso.

O benefício mais valioso do uso de um contêiner de IoC é que você pode ter um comutador de configuração em um único local que permite alternar entre, digamos, modo de teste e modo de produção. Por exemplo, suponha que você tenha duas versões de suas classes de acesso ao banco de dados ... uma versão que registrou agressivamente e executou muita validação, que você usou durante o desenvolvimento, e outra versão sem log ou validação que foi extremamente rápida para produção. É bom poder alternar entre eles em um só lugar. Por outro lado, esse é um problema bastante trivial, fácil de lidar de maneira mais simples, sem a complexidade dos contêineres de IoC.

Acredito que se você usar contêineres IoC, seu código se tornará, francamente, muito mais difícil de ler. O número de lugares que você deve procurar para descobrir o que o código está tentando fazer aumenta em pelo menos um. E em algum lugar no céu um anjo clama.

Joel Spolsky
fonte
81
Eu acho que você confundiu um pouco Joel. IoC e contêineres vieram primeiro, depois veio o tratado de Martin, e não vice-versa.
Glenn Block
55
A maioria dos contêineres permite que você vá rapidamente. Com autofac, mapa de estrutura, unidade, ninject, etc., você poderá ter o contêiner funcionando em cerca de 5 minutos. Sim, eles têm recursos avançados, mas você não precisa deles para decolar.
Glenn Block
58
Joel, eu pensava da mesma maneira que você. Então, eu literalmente e seriamente gastei cinco minutos para descobrir como obter a configuração mais básica em execução e fiquei impressionado com a regra 80/20 em ação. Torna o código muito mais limpo, especialmente nos casos simples.
23726 Justin Bozonier
55
Venho programando há menos de dois anos e li o wiki do Ninject e entendi. Eu tenho usado DI em muitos lugares desde então. Você viu aquilo? Menos de dois anos e lendo algumas páginas em um wiki. Eu não sou um mago ou algo assim. Não me considero um gênio, mas foi fácil para mim entender. Não consigo imaginar alguém que realmente entenda OO não sendo capaz de entender. Além disso, a quais manuais da página 200-300 você está se referindo? Eu nunca vi isso. Todos os contêineres IoC que usei são bem curtos e direto ao ponto.
JR Garcia
35
+1 bem escrito e pensado. Uma coisa que acho que muitas pessoas não percebem é que a adição de uma estrutura ou outra para "magicamente" lidar com algo adiciona automaticamente complexidade e limitações a um sistema. Às vezes vale a pena, mas geralmente algo é esquecido e a entropia é liberada no código, tentando corrigir um problema imposto por limitações. Isso pode economizar tempo, mas as pessoas precisam entender que isso pode custar 100 vezes mais, tentando consertar algum problema obscuro. Apenas um punhado de pessoas realmente tem conhecimento suficiente para resolver.
kemiller2002
37

Presumivelmente, ninguém está forçando você a usar uma estrutura de contêiner de DI. Você já está usando o DI para desacoplar suas classes e melhorar a testabilidade, por isso está obtendo muitos dos benefícios. Em resumo, você está favorecendo a simplicidade, o que geralmente é uma coisa boa.

Se o seu sistema atingir um nível de complexidade em que a DI manual se torna uma tarefa (ou seja, aumenta a manutenção), avalie-a na curva de aprendizado da equipe de uma estrutura de contêiner de DI.

Se você precisar de mais controle sobre o gerenciamento do tempo de vida da dependência (ou seja, se sentir a necessidade de implementar o padrão Singleton), consulte os contêineres DI.

Se você usar um contêiner de DI, use apenas os recursos necessários. Pule o arquivo de configuração XML e configure-o no código, se isso for suficiente. Atenha-se à injeção do construtor. Os princípios básicos do Unity ou do StructureMap podem ser resumidos em algumas páginas.

Há um ótimo post de Mark Seemann sobre o assunto: Quando usar um DI Container

TrueWill
fonte
Resposta muito boa. A única coisa em que eu divergiria de opinião é se a injeção manual NUNCA pode ou não ser considerada menos complexa ou não uma tarefa.
7409 wekempf
+1. Uma das melhores respostas aqui. Obrigado também pelo link do artigo do blog.
stakx - não está mais contribuindo em 27/10
1
+1 para o ponto de vista equilibrado. Os contêineres de IoC nem sempre são bons e nem sempre são ruins. Se você não vê uma necessidade, não a use. Apenas saiba que a ferramenta está lá se você vir uma necessidade.
28414 Phil
32

Na minha opinião, o principal benefício de uma IoC é a capacidade de centralizar a configuração de suas dependências.

Se você estiver usando a injeção de dependência no momento, seu código pode ser assim

public class CustomerPresenter
{
  public CustomerPresenter() : this(new CustomerView(), new CustomerService())
  {}

  public CustomerPresenter(ICustomerView view, ICustomerService service)
  {
    // init view/service fields
  }
  // readonly view/service fields
}

Se você usou uma classe IoC estática, ao contrário dos arquivos de configuração IMHO, mais confusos, pode ter algo como isto:

public class CustomerPresenter
{
  public CustomerPresenter() : this(IoC.Resolve<ICustomerView>(), IoC.Resolve<ICustomerService>())
  {}

  public CustomerPresenter(ICustomerView view, ICustomerService service)
  {
    // init view/service fields
  }
  // readonly view/service fields
}

Então, sua classe Static IoC ficaria assim: estou usando o Unity aqui.

public static IoC
{
   private static readonly IUnityContainer _container;
   static IoC()
   {
     InitializeIoC();
   }

   static void InitializeIoC()
   {
      _container = new UnityContainer();
      _container.RegisterType<ICustomerView, CustomerView>();
      _container.RegisterType<ICustomerService, CustomerService>();
      // all other RegisterTypes and RegisterInstances can go here in one file.
      // one place to change dependencies is good.
   }
}
Bendewey
fonte
11
Ben, o que você está falando é realmente o Local do Serviço, não a Injeção de Dependência - há uma diferença. Eu sempre prefiro o primeiro sobre o último. stevenharman.net/blog/archive/2009/09/25/…
stevenharman
23
Em vez de executar o IoC.Resolve <T> no construtor padrão, você deve aproveitar a capacidade do contêiner do IOC de construir o objeto para você. Livre-se desse construtor vazio e deixe o contêiner do IOC construir o objeto para você. apresentador var = IoC.Resolve <CustomerPresenter> (); voila! todas as dependências já conectadas.
21459 Ben Scheirman
12
O lado negativo da centralização desse tipo de configuração é a descontextualização do conhecimento. Joel aponta esse problema ao dizer: "o código se torna, francamente, muito mais difícil de ler".
7269 Scott Bellware
6
@ sbellware, que conhecimento? A interface usada como dependência primordial serve como contrato e deve ser suficiente para transmitir seu objetivo e comportamento, não? Suponho que você esteja se referindo ao conhecimento adquirido com a introdução de contratos na execução do contrato; nesse caso, você precisará rastrear a configuração apropriada? Eu confio em um computador (R # no VS, IntelliJ, etc.) para isso, pois eles são ótimos na navegação de nós e arestas de um gráfico; Reservo a pilha do meu cérebro para o contexto do local de trabalho no qual estou focado.
22410 stevenharman
7
Steve, eu conheço o texto .NET e andei nessa caminhada por muitos anos. Também tenho familiaridade íntima com outros modos e formas e entendo visceralmente que existem trocas reais. Eu sei como e quando fazer essas trocas, mas a diversidade na minha vida profissional pelo menos me permite entender de maneira tangível a perspectiva de Joel. Em vez de me ensinar sobre ferramentas e truques que venho usando há mais tempo do que a maioria dos monocultores do .NET, diga-me algo que não sei. Reservo a pilha do meu cérebro para pensamentos diversos e críticos, em vez da estase e ortodoxia do alt.net.
Scott Bellware
32

Os contêineres IoC também são bons para carregar dependências de classe profundamente aninhadas. Por exemplo, se você tivesse o seguinte código usando a Injeção de Depedência.

public void GetPresenter()
{
    var presenter = new CustomerPresenter(new CustomerService(new CustomerRepository(new DB())));
}

class CustomerPresenter
{
    private readonly ICustomerService service;
    public CustomerPresenter(ICustomerService service)
    {
        this.service = service;
    }
}

class CustomerService
{
    private readonly IRespository<Customer> repository;
    public CustomerService(IRespository<Customer> repository)
    {
        this.repository = repository;
    }
}

class CustomerRepository : IRespository<Customer>
{
    private readonly DB db;
    public CustomerRepository(DB db)
    {
        this.db = db;
    }
}

class DB { }

Se todas essas dependências fossem carregadas no contêiner de IoC, você poderia resolver o CustomerService e todas as dependências filhas serão resolvidas automaticamente.

Por exemplo:

public static IoC
{
   private IUnityContainer _container;
   static IoC()
   {
       InitializeIoC();
   }

   static void InitializeIoC()
   {
      _container = new UnityContainer();
      _container.RegisterType<ICustomerService, CustomerService>();
      _container.RegisterType<IRepository<Customer>, CustomerRepository>();
   }

   static T Resolve<T>()
   {
      return _container.Resolve<T>();
   }
}

public void GetPresenter()
{
   var presenter = IoC.Resolve<CustomerPresenter>();
   // presenter is loaded and all of its nested child dependencies 
   // are automatically injected
   // -
   // Also, note that only the Interfaces need to be registered
   // the concrete types like DB and CustomerPresenter will automatically 
   // resolve.
}
Bendewey
fonte
Mas a classe IoC é chamada usando o antipadrão do localizador de serviço. O container_container deve ser passado usando o construtor DI em vez de chamar T Resolve <T> () estático?
22713 Michael Freidgeim
@bendewey Seu exemplo implica que ele passa automaticamente os argumentos aos construtores para o novo CustomerService e o novo CustomerRepository. É assim que funciona? E se você tiver outros parâmetros adicionais também?
precisa saber é o seguinte
28

Sou fã de programação declarativa (veja quantas perguntas SQL respondo), mas os contêineres de IoC que olhei parecem muito misteriosos para seu próprio bem.

... ou talvez os desenvolvedores de contêineres IoC sejam incapazes de escrever documentação clara.

... ou então ambos são verdadeiros em um grau ou outro.

Não acho que o conceito de um contêiner de IoC seja ruim. Mas a implementação deve ser poderosa (ou seja, flexível) o suficiente para ser útil em uma ampla variedade de aplicativos, mas simples e fácil de entender.

Provavelmente são seis de uma e meia dúzia da outra. Um aplicativo real (não um brinquedo ou demonstração) é obrigado a ser complexo, respondendo por muitos casos extremos e exceções às regras. Você encapsula essa complexidade no código imperativo ou no código declarativo. Mas você tem que representá-lo em algum lugar.

Bill Karwin
fonte
5
Gosto da sua observação sobre a presença inevitável da complexidade. A diferença entre a fiação de dependência manual e baseada em contêiner é que, usando um contêiner, você pode se concentrar em cada componente individualmente e, assim, gerenciar a crescente complexidade de maneira bastante linear. Sistemas com fiação de dependência manual são mais difíceis de evoluir, pois é difícil isolar o ato de configurar um componente da configuração de todo o resto.
Nicholas Blumhardt
Eu gosto do AspectJ. Não é Java, mas também não é XML. É uma espécie de IS Java, na medida em que se sente e se parece com Java por ser uma extensão dela. Prefiro manter meus aspectos fora dos meus POJOs e na minha lógica de negócios. Eu quase gostaria de manter meus IoCs também. Existe muito XML demais na conexão de ferramentas, IMHO. Se eu precisar ter arquivos de configuração, sejam scripts do BeanShell. / Java, aplique suas .Net workalikes aqui ->.
Chris K
2
Certamente entendo sua resposta, mas acho que grande parte do problema é que muitas das estruturas de IoC (e, na verdade, muitas outras estruturas para outras coisas também) são descritas e documentadas para outras pessoas já familiarizadas com o conceitos e terminologia necessários. Estou aprendendo sobre muito disso ter acabado de herdar o código de outro programador. Estou lutando para entender por que o código está usando a IoC quando os 'gráficos de objetos' são razoavelmente pequenos e não há cobertura de teste real do código. Como programador SQL, você provavelmente poderá apreciar melhor o uso da IoC com um ORM.
Kenny Evitt
1
@KennyEvitt, bom ponto, parece prematuro usar uma estrutura de IoC se alguém tiver um aplicativo simples e nem sequer se deu ao trabalho de ter uma boa cobertura de teste.
Bill Karwin
27

O uso de um contêiner é principalmente sobre a mudança de um estilo imperativo / com script de inicialização e configuração para um declarativo . Isso pode ter alguns efeitos benéficos diferentes:

  • Reduzindo as rotinas de inicialização do programa principal do hairball .
  • Ativando recursos de reconfiguração bastante profundos no tempo de implantação.
  • Fazer do estilo injetável por dependência o caminho de menor resistência para novos trabalhos.

Obviamente, pode haver dificuldades:

  • Código que requer gerenciamento complexo de inicialização / encerramento / ciclo de vida pode não ser facilmente adaptado a um contêiner.
  • Você provavelmente terá que navegar por qualquer problema pessoal, de processo e de cultura da equipe - mas, foi por isso que perguntou ...
  • Alguns dos kits de ferramentas estão rapidamente se tornando pesados, incentivando o tipo de dependência profunda com a qual muitos contêineres de DI começaram como uma reação.
Jeffrey Hantin
fonte
23

Parece-me que você já construiu seu próprio contêiner de IoC (usando os vários padrões descritos por Martin Fowler) e está perguntando por que a implementação de outra pessoa é melhor que a sua.

Então, você tem um monte de código que já funciona. E está se perguntando por que você deseja substituí-lo pela implementação de outra pessoa.

Prós por considerar um contêiner de IoC de terceiros

  • Você corrige bugs gratuitamente
  • O design da biblioteca pode ser melhor que o seu
  • As pessoas já devem estar familiarizadas com a biblioteca específica
  • A biblioteca pode ser mais rápida que a sua
  • Pode ter alguns recursos que você deseja implementar, mas nunca teve tempo (você tem um localizador de serviço?)

Contras

  • Você recebe bugs introduzidos, de graça :)
  • O design da biblioteca pode ser pior que o seu
  • Você precisa aprender uma nova API
  • Muitos recursos que você nunca usará
  • Geralmente é mais difícil depurar código que você não escreveu
  • Migrar de um contêiner IoC anterior pode ser entediante

Portanto, avalie seus profissionais contra seus contras e tome uma decisão.

Sam Saffron
fonte
Eu concordo com você. Sam - DI é um tipo de IoC, por isso, se o pôster original está fazendo DI, eles já estão fazendo IoC, então a verdadeira questão é por que fazer o seu próprio.
Jamie Amor
3
Discordo. O COI é uma maneira de fazer DI. Os contêineres de IOC fazem DI, controlando a instanciação de tipo usando a reflexão de tempo de execução dinâmico para satisfazer e injetar dependências. O OP está sugerindo que ele está fazendo DI estático e está ponderando por que ele precisa trocar os benefícios da verificação do tempo de compilação pelos benefícios geralmente associados à reflexão de tempo de execução dinâmico do COI.
precisa saber é o seguinte
17

Eu acho que a maior parte do valor de uma IoC é obtida usando DI. Como você já está fazendo isso, o restante do benefício é incremental.

O valor que você obtém dependerá do tipo de aplicativo em que você está trabalhando:

  • Para multilocação, o contêiner de IoC pode cuidar de parte do código de infraestrutura para carregar diferentes recursos do cliente. Quando você precisar de um componente específico do cliente, use um seletor personalizado para lidar com a lógica e não se preocupe com isso no código do cliente. Certamente você pode criar isso sozinho, mas aqui está um exemplo de como uma IoC pode ajudar.

  • Com muitos pontos de extensibilidade, o IoC pode ser usado para carregar componentes da configuração. Isso é algo comum de construir, mas as ferramentas são fornecidas pelo contêiner.

  • Se você deseja usar o AOP para algumas questões transversais, o IoC fornece ganchos para interceptar invocações de métodos. Isso é menos comum em projetos ad-hoc, mas a IoC facilita.

Eu escrevi uma funcionalidade como essa antes, mas se precisar de algum desses recursos agora, prefiro usar uma ferramenta pré-criada e testada, se ela se encaixar na minha arquitetura.

Conforme mencionado por outras pessoas, você também pode configurar centralmente quais classes deseja usar. Embora isso possa ser uma coisa boa, tem um custo de desorientação e complicação. Os principais componentes da maioria dos aplicativos não são substituídos muito, portanto, a troca é um pouco mais difícil de fazer.

Eu uso um contêiner de IoC e aprecio a funcionalidade, mas tenho que admitir que notei o trade-off: Meu código se torna mais claro no nível da classe e menos claro no nível do aplicativo (ou seja, visualizando o fluxo de controle).

Steven Lyons
fonte
16

Sou um viciado em recuperação do COI. Estou achando difícil justificar o uso do COI para DI na maioria dos casos atualmente. Os contêineres do IOC sacrificam a verificação do tempo de compilação e, em troca, oferecem uma configuração "fácil", gerenciamento complexo da vida útil e descoberta dinâmica de dependências em tempo de execução. Acho que a perda da verificação do tempo de compilação e as exceções / mágicas resultantes do tempo de execução não valem nada na maioria dos casos. Em aplicativos corporativos de grande porte, eles podem dificultar o acompanhamento do que está acontecendo.

Eu não compro o argumento de centralização porque você pode centralizar a configuração estática com muita facilidade, usando uma fábrica abstrata para seu aplicativo e adiando religiosamente a criação de objetos para a fábrica abstrata, ou seja, faça o DI adequado.

Por que não fazer o DI estático sem magia como este:

interface IServiceA { }
interface IServiceB { }
class ServiceA : IServiceA { }
class ServiceB : IServiceB { }

class StubServiceA : IServiceA { }
class StubServiceB : IServiceB { }

interface IRoot { IMiddle Middle { get; set; } }
interface IMiddle { ILeaf Leaf { get; set; } }
interface ILeaf { }

class Root : IRoot
{
    public IMiddle Middle { get; set; }

    public Root(IMiddle middle)
    {
        Middle = middle;
    }

}

class Middle : IMiddle
{
    public ILeaf Leaf { get; set; }

    public Middle(ILeaf leaf)
    {
        Leaf = leaf;
    }
}

class Leaf : ILeaf
{
    IServiceA ServiceA { get; set; }
    IServiceB ServiceB { get; set; }

    public Leaf(IServiceA serviceA, IServiceB serviceB)
    {
        ServiceA = serviceA;
        ServiceB = serviceB;
    }
}


interface IApplicationFactory
{
    IRoot CreateRoot();
}

abstract class ApplicationAbstractFactory : IApplicationFactory
{
    protected abstract IServiceA ServiceA { get; }
    protected abstract IServiceB ServiceB { get; }

    protected IMiddle CreateMiddle()
    {
        return new Middle(CreateLeaf());
    }

    protected ILeaf CreateLeaf()
    {
        return new Leaf(ServiceA,ServiceB);
    }


    public IRoot CreateRoot()
    {
        return new Root(CreateMiddle());
    }
}

class ProductionApplication : ApplicationAbstractFactory
{
    protected override IServiceA ServiceA
    {
        get { return new ServiceA(); }
    }

    protected override IServiceB ServiceB
    {
        get { return new ServiceB(); }
    }
}

class FunctionalTestsApplication : ApplicationAbstractFactory
{
    protected override IServiceA ServiceA
    {
        get { return new StubServiceA(); }
    }

    protected override IServiceB ServiceB
    {
        get { return new StubServiceB(); }
    }
}


namespace ConsoleApplication5
{
    class Program
    {
        static void Main(string[] args)
        {
            var factory = new ProductionApplication();
            var root = factory.CreateRoot();

        }
    }

    //[TestFixture]
    class FunctionalTests
    {
        //[Test]
        public void Test()
        {
            var factory = new FunctionalTestsApplication();
            var root = factory.CreateRoot();
        }
    }
}

Sua configuração de contêiner é sua implementação abstrata de fábrica, seus registros são implementações de membros abstratos. Se você precisar de uma nova dependência de singleton, basta adicionar outra propriedade abstrata à fábrica abstrata. Se você precisar de uma dependência transitória, basta adicionar outro método e injetar como um Func <>.

Vantagens:

  • Todas as configurações e a criação de objetos são centralizadas.
  • A configuração é apenas código
  • A verificação do tempo de compilação facilita a manutenção, pois você não pode esquecer de atualizar seus registros.
  • Nenhuma mágica de reflexão em tempo de execução

Eu recomendo os céticos para dar um próximo projeto de campo verde e honestamente se perguntar em que momento você precisa do contêiner. É fácil levar em consideração um contêiner IOC mais tarde, pois você está substituindo uma implementação de fábrica por um módulo de configuração IOC Container.

Maxm007
fonte
Isso não me parece um argumento contra a IoC, mas um argumento contra as estruturas configuráveis ​​da IoC. Suas fábricas aqui não se resumem a IoCs simples e codificadas ?
Mr.Mindor 02/08/19
Acho que quando o pôster mencionou IoC, uma estrutura de contêiner IoC foi criada. Obrigado pela redação original, isso ajuda a me convencer a ficar sem uma estrutura. Então, se é configurável em testes e em código de produção, qual é a vantagem de uma estrutura "configurável"?
huggie
Pelo meu entendimento da terminologia, Inversion Of Control significa encontrar dependências no tempo de execução. Então, sim, você poderia dizer que a fábrica é basicamente um contêiner codificado ou estático, mas não é um contêiner do COI. Os tipos são resolvidos em tempo de compilação. É um argumento contra o uso de estruturas configuráveis ​​que usam pesquisas de dicionário para resolver tipos em tempo de execução.
Maxm007
14

O maior benefício do uso de contêineres IoC para mim (pessoalmente, eu uso o Ninject) foi eliminar o repasse de configurações e outros tipos de objetos de estado global.

Eu não programo para a web, o meu é um aplicativo de console e, em muitos lugares da árvore de objetos, preciso ter acesso às configurações ou metadados especificados pelo usuário, criados em uma ramificação completamente separada da árvore de objetos. Com o IoC, eu simplesmente digo ao Ninject para tratar as configurações como um singleton (porque sempre há apenas uma instância delas), solicito as configurações ou o dicionário no construtor e pronto ... elas aparecem magicamente quando eu precisar delas!

Sem usar um contêiner de IoC, eu teria que passar as configurações e / ou metadados para 2, 3, ..., n objetos antes de ser realmente usado pelo objeto que precisava dele.

Existem muitos outros benefícios para os contêineres de DI / IoC, já que outras pessoas detalharam aqui, e passar da ideia de criar objetos para solicitar objetos pode ser alucinante, mas usar o DI foi muito útil para mim e minha equipe, então talvez você possa adicioná-lo para o seu arsenal!

Jeffrey Cameron
fonte
2
Esta é uma enorme vantagem. Não posso acreditar que mais ninguém tenha mencionado isso antes. Sem a necessidade de passar ou armazenar objetos temporários, o código fica muito mais limpo.
Finglas
1
Os objetos globais @qble funcionam muito bem ... se você deseja que sua base de códigos seja um pesadelo que não pode ser testado e que está cada vez mais difícil de modificar ao longo do tempo. boa sorte com isso.
11133 Jeffrey Cameron
2
@JeffreyCameron IoC Container é um objeto global. Não remove suas dependências globais.
qble
3
É verdade, no entanto, o contêiner de IoC é transparente para o restante do aplicativo. Você chama uma vez na inicialização para obter uma instância e é a última vez que a vê. Com objetos globais seu código está repleta de dependências rígidos
Jeffrey Cameron
1
O @qble usa fábricas para criar instâncias transitórias em tempo real. As fábricas são serviços únicos. Se as instâncias transitórias precisarem de algo do contêiner IOC, conecte-as à fábrica e faça com que a fábrica passe a dependência para a instância transitória.
Jeffrey Cameron
14

As estruturas de IoC são excelentes se você quiser ...

  • ... jogue fora a segurança do tipo. Muitas estruturas de IoC (todas?) Obrigam você a executar o código se quiser ter certeza de que tudo está conectado corretamente. "Ei! Espero que eu tenha configurado tudo para que minhas inicializações dessas 100 classes não falhem na produção, lançando exceções de ponteiro nulo!"

  • ... ninhada seu código com globals (quadros IoC são todos sobre a mudança de estados globais).

  • ... escreva códigos ruins com dependências pouco claras que são difíceis de refatorar, pois você nunca saberá o que depende de quê.

O problema com a IoC é que as pessoas que as usam costumam escrever código como este

public class Foo {
    public Bar Apa {get;set;}
    Foo() {
        Apa = new Bar();
    }
}

o que é obviamente falho, uma vez que a dependência entre Foo e Bar é muito forte. Então eles perceberam que seria melhor escrever código como

public class Foo {
    public IBar Apa {get;set;}
    Foo() {
        Apa = IoC<IBar>();
    }
}

que também é falho, mas menos obviamente. Em Haskell, o tipo de Foo()seria, IO Foomas você realmente não quer a IOparte e deve ser um sinal de aviso de que algo está errado com o seu design, se você o conseguiu.

Para se livrar dele (a parte IO), obtenha todas as vantagens das estruturas de IoC e nenhuma de suas desvantagens, você poderia usar uma fábrica abstrata.

A solução correta seria algo como

data Foo = Foo { apa :: Bar }

ou talvez

data Foo = forall b. (IBar b) => Foo { apa :: b }

e injetar (mas eu não chamaria de injetar).

Também: veja este vídeo com Erik Meijer (inventor do LINQ), onde ele diz que o DI é para pessoas que não sabem matemática (e eu não poderia concordar mais): http://www.youtube.com/watch?v = 8Mttjyf-8P4

Ao contrário do Sr. Spolsky, não acredito que as pessoas que usam estruturas de IoC sejam muito inteligentes - simplesmente acredito que não sabem matemática.

finnsson
fonte
9
jogue fora a segurança do tipo - exceto que o seu exemplo ainda é do tipo seguro, é essa a interface. Você não está passando tipos de objetos, mas o tipo de interface. E a segurança de tipo não elimina as exceções de referência nula, você pode passar referências nulas como parâmetros de segurança de tipo - isso não é um problema de segurança de tipo.
blowdart 13/10/09
Você joga fora a segurança do tipo, pois não sabe se o tipo está conectado ou não no contêiner de IoC (o tipo de IoC <IBar> não é IBar, é de fato IO IBar) .. E sim, no exemplo de Haskell Não consigo obter uma referência nula.
13139 finnsson
Digite segurança não realmente sobre um objeto que está sendo conectado, pelo menos não em Java / C #, é simplesmente sobre o objeto ser do tipo certo. E ConcreteClass myClass = null ainda é do tipo certo. Se você deseja aplicar não nulo, os contratos de código entram em jogo.
blowdart
2
Concordo com o seu primeiro ponto, até certo ponto, gostaria de alguma explicação do segundo e do terceiro nunca foi um problema para mim. Seu exemplo de C # usa o contêiner de IoC como localizador de serviço, um erro comum. E comparando C # vs Haskell = maçãs vs laranjas.
Mauricio Scheffer
2
Você não joga fora a segurança de tipo. Alguns contêineres DI (se não a maioria) permitem verificar o gráfico completo do objeto após o processo de registro. Ao fazer isso, você pode impedir que o aplicativo inicie com uma configuração incorreta (falha rápida) e meus aplicativos sempre têm alguns testes de unidade que chamam a configuração de produção para permitir que eu encontre esses erros durante o teste. Aconselho a todos que usam um contêiner de IoC para escrever alguns testes de unidade que garantem que todos os objetos de nível superior possam ser resolvidos.
6113 Steven
10

Descobri que implementar corretamente a Injeção de Dependências tende a forçar os programadores a usar uma variedade de outras práticas de programação que ajudam a melhorar a testabilidade, flexibilidade, manutenibilidade e escalabilidade do código: práticas como o Princípio de Responsabilidade Única, Separações de Preocupações e codificação contra APIs. Parece que estou sendo obrigado a escrever classes e métodos mais modulares, do tamanho de uma mordida, o que facilita a leitura do código, porque pode ser usado em pedaços de mordida.

Mas também tende a criar árvores de dependência bastante grandes, que são gerenciadas com muito mais facilidade por meio de uma estrutura (especialmente se você usar convenções) do que manualmente. Hoje eu queria testar algo muito rapidamente no LINQPad, e achei que seria muito complicado criar um kernel e carregar meus módulos, e acabei escrevendo isso manualmente:

var merger = new SimpleWorkflowInstanceMerger(
    new BitFactoryLog(typeof(SimpleWorkflowInstanceMerger).FullName), 
    new WorkflowAnswerRowUtil(
        new WorkflowFieldAnswerEntMapper(),
        new ActivityFormFieldDisplayInfoEntMapper(),
        new FieldEntMapper()),
    new AnswerRowMergeInfoRepository());

Em retrospecto, seria mais rápido usar a estrutura de IoC, uma vez que os módulos definem praticamente todas essas coisas por convenção.

Depois de passar algum tempo estudando as respostas e os comentários sobre essa pergunta, estou convencido de que as pessoas que se opõem ao uso de um contêiner de IoC não estão praticando a injeção de dependência verdadeira. Os exemplos que eu vi são de práticas que geralmente são confundidas com injeção de dependência. Algumas pessoas estão reclamando de dificuldade em "ler" o código. Se feito corretamente, a grande maioria do seu código deve ser idêntica ao usar o DI manualmente, como ao usar um contêiner de IoC. A diferença deve residir inteiramente em alguns "pontos de inicialização" dentro do aplicativo.

Em outras palavras, se você não gosta de contêineres IoC, provavelmente não está fazendo a Injeção de Dependência da maneira que deveria ser feita.

Outro ponto: a injeção de dependência realmente não pode ser feita manualmente, se você usar a reflexão em qualquer lugar. Embora eu odeie o que a reflexão faz para codificar a navegação, você precisa reconhecer que há certas áreas em que ela realmente não pode ser evitada. O ASP.NET MVC, por exemplo, tenta instanciar o controlador via reflexão em cada solicitação. Para fazer a injeção de dependência manualmente, você teria que tornar todos os controladores uma "raiz de contexto", assim:

public class MyController : Controller
{
    private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
    public MyController()
    {
        _simpleMerger = new SimpleWorkflowInstanceMerger(
            new BitFactoryLog(typeof(SimpleWorkflowInstanceMerger).FullName), 
            new WorkflowAnswerRowUtil(
                new WorkflowFieldAnswerEntMapper(),
                new ActivityFormFieldDisplayInfoEntMapper(),
                new FieldEntMapper()),
            new AnswerRowMergeInfoRepository())
    }
    ...
}

Agora compare isso com permitir que uma estrutura DI faça isso por você:

public MyController : Controller
{
    private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
    public MyController(ISimpleWorkflowInstanceMerger simpleMerger)
    {
        _simpleMerger = simpleMerger;
    }
    ...
}

Usando uma estrutura DI, observe que:

  • Eu posso testar esta unidade. Ao criar um mock ISimpleWorkflowInstanceMerger, posso testar se ele é usado da maneira que antecipo, sem a necessidade de uma conexão com o banco de dados ou qualquer coisa.
  • Eu uso muito menos código, e o código é muito mais fácil de ler.
  • Se uma das dependências da minha dependência for alterada, não preciso fazer nenhuma alteração no controlador. Isso é especialmente interessante quando você considera que é provável que vários controladores usem algumas das mesmas dependências.
  • Eu nunca referencio explicitamente classes da minha camada de dados. Meu aplicativo da web pode incluir apenas uma referência ao projeto que contém a ISimpleWorkflowInstanceMergerinterface. Isso me permite dividir o aplicativo em módulos separados e manter uma verdadeira arquitetura multicamada, o que, por sua vez, torna as coisas muito mais flexíveis.

Um aplicativo Web típico terá muitos controladores. Toda a dor de fazer DI manualmente em cada controlador realmente aumentará à medida que o aplicativo crescer. Se você tiver um aplicativo com apenas uma raiz de contexto, que nunca tenta instanciar um serviço por reflexão, isso não é um problema tão grande. No entanto, qualquer aplicativo que use Injeção de Dependência se tornará extremamente caro para gerenciar quando atingir um determinado tamanho, a menos que você use algum tipo de estrutura para gerenciar o gráfico de dependência.

StriplingWarrior
fonte
9

Sempre que você usa a palavra-chave "new", está criando uma dependência de classe concreta e um pequeno alarme deve soar na sua cabeça. Torna-se mais difícil testar esse objeto isoladamente. A solução é programar para interfaces e injetar a dependência para que o objeto possa ser testado em unidade com qualquer coisa que implemente essa interface (por exemplo, zombarias).

O problema é que você precisa construir objetos em algum lugar. Um padrão de fábrica é uma maneira de mudar o acoplamento de seus POXOs (Plain Old "insira sua linguagem OO aqui" Objetos). Se você e seus colegas de trabalho estão escrevendo código como esse, um contêiner de IoC é a próxima "Melhoria Incremental" que você pode fazer na sua base de código. Isso mudará todo esse código desagradável do padrão da Fábrica para fora de seus objetos limpos e da lógica de negócios. Eles vão entender e amar. Caramba, fale para a empresa sobre o motivo de você adorar e deixe todo mundo entusiasmado.

Se seus colegas de trabalho ainda não estão trabalhando com DI, sugiro que você se concentre nisso primeiro. Divulgue como escrever um código limpo que seja facilmente testável. O código DI limpo é a parte mais difícil, quando você estiver lá, mudar a lógica de fiação de objetos das classes Factory para um contêiner de IoC deve ser relativamente trivial.

queimaduras mate
fonte
8

Como todas as dependências são claramente visíveis, promove a criação de componentes que são fracamente acoplados e ao mesmo tempo facilmente acessíveis e reutilizáveis ​​em todo o aplicativo.

bbmud
fonte
7

Você não precisa de um contêiner de IoC.

Mas se você seguir rigorosamente um padrão de DI, verá que ter um deles removerá uma tonelada de código redundante e chato.

De qualquer maneira, esse é o melhor momento para usar uma biblioteca / estrutura - quando você entende o que está fazendo e pode fazê-lo sem a biblioteca.

kyoryu
fonte
6

Por acaso, eu estou no processo de obter código DI desenvolvido em casa e substituí-lo por um COI. Provavelmente removi bem mais de 200 linhas de código e substituí-o por cerca de 10. Sim, tive que aprender um pouco sobre como usar o contêiner (Winsor), mas sou um engenheiro que trabalha em tecnologias da Internet no Século 21, então estou acostumado a isso. Eu provavelmente gastei cerca de 20 minutos examinando os procedimentos. Valeu a pena o meu tempo.

Matt Wrock
fonte
3
"mas eu sou um engenheiro trabalhando em tecnologias de internet no século 21, então estou acostumado a isso" -> 1
user96403
5

À medida que você continua a dissociar suas classes e inverter suas dependências, as classes continuam pequenas e o "gráfico de dependências" continua aumentando de tamanho. (Isso não é ruim.) O uso de recursos básicos de um contêiner de IoC torna a conexão de todos esses objetos trivial, mas fazê-lo manualmente pode ser muito oneroso. Por exemplo, e se eu quiser criar uma nova instância de "Foo", mas precisar de uma "Barra". E um "Bar" precisa de um "A", "B" e "C". E cada um deles precisa de 3 outras coisas, etc etc. (sim, não consigo criar bons nomes falsos :)).

O uso de um contêiner de IoC para criar seu gráfico de objetos reduz a complexidade em uma tonelada e a coloca na configuração única. Eu simplesmente digo "crie-me um 'Foo'" e descobre o que é necessário para criar um.

Algumas pessoas usam os contêineres IoC para muito mais infraestrutura, o que é bom para cenários avançados, mas nesses casos eu concordo que ele pode ofuscar e dificultar a leitura e depuração de códigos para novos desenvolvedores.

Aaron Lerch
fonte
1
por que continuamos a apaziguar o menor denominador comum em vez de trabalhar para levantá-lo?
21910 stevenharman
4
Eu não disse nada sobre apaziguar. Acabei de dizer que os cenários avançados podem tornar as coisas mais ofuscadas para novos desenvolvedores - isso é um fato. Eu não disse "então não faça". Mas, mesmo assim (tempo para o advogado do diabo), muitas vezes existem outras maneiras de realizar a mesma coisa que torna uma base de código mais explícita e acessível. Esconder a complexidade na configuração personalizada da ferramenta de infraestrutura apenas porque você não pode ser o motivo certo e isso não atrai ninguém.
Aaron Lerch
4

Dittos sobre Unity. Ficar grande demais e você pode ouvir o rangido nas vigas.

Isso nunca me surpreende quando as pessoas começam a falar sobre a aparência do código IoC limpo. São os mesmos tipos de pessoas que falaram sobre como os modelos em C ++ eram a maneira elegante de voltar nos anos 90, mas hoje em dia os consideram misteriosos. . Bah!

M Lopez
fonte
2

No mundo .NET, o AOP não é muito popular; portanto, para o DI, uma estrutura é sua única opção real, seja você mesmo escrevendo uma ou usando outra estrutura.

Se você usou o AOP, pode injetar ao compilar seu aplicativo, o que é mais comum em Java.

Existem muitos benefícios para o DI, como o acoplamento reduzido, para facilitar o teste de unidade, mas como você o implementará? Deseja usar a reflexão para fazer isso sozinho?

James Black
fonte
A nova estrutura do MEF permite exatamente isso para o .NET. Devo dizer que o código é mais limpo e fácil de entender, facilita muito o entendimento do conceito de DI.
Terjetyl 07/10/09
4
@TT: MEF não é um contêiner de injeção de dependência e não tem nada a ver com AOP.
Mauricio Scheffer
2

Então, já faz quase 3 anos, não é?

  1. 50% dos que comentaram a favor das estruturas de IoC não entendem a diferença entre as estruturas de IoC e IoC. Duvido que eles saibam que você pode escrever código sem ser implantado em um servidor de aplicativos

  2. Se usarmos a estrutura Java Spring mais popular, é a configuração da IoC movida do XMl para o código e agora parece com isso

`@Configuration public class AppConfig {

public @Bean TransferService transferService() {
    return new TransferServiceImpl(accountRepository());
}

public @Bean AccountRepository accountRepository() {
    return new InMemoryAccountRepository();
}

} `E precisamos de uma estrutura para fazer isso, por que exatamente?

Ivan
fonte
1

Honestamente, não acho que existam muitos casos em que os contêineres IoC são necessários e, na maioria das vezes, eles apenas adicionam complexidade desnecessária.

Se você o estiver usando apenas para simplificar a construção de um objeto, eu teria que perguntar: você está instanciando esse objeto em mais de um local? Um singleton não atenderia às suas necessidades? Você está alterando a configuração em tempo de execução? (Alternando tipos de fonte de dados, etc).

Se sim, você pode precisar de um contêiner de IoC. Caso contrário, você está apenas afastando a inicialização de onde o desenvolvedor pode vê-la facilmente.

Quem disse que uma interface é melhor que herança, afinal? Digamos que você esteja testando um serviço. Por que não usar o construtor DI e criar zombarias das dependências usando herança? A maioria dos serviços que utilizo possui apenas algumas dependências. Fazer testes de unidade dessa maneira impede a manutenção de inúmeras interfaces inúteis e significa que você não precisa usar o Resharper para encontrar rapidamente a declaração de um método.

Acredito que, na maioria das implementações, dizer que os contêineres IoC removem códigos desnecessários é um mito.

Primeiro, é necessário configurar o contêiner. Então você ainda precisa definir cada objeto que precisa ser inicializado. Para não salvar o código na inicialização, você o move (a menos que seu objeto seja usado mais de uma vez. É melhor como um Singleton?). Então, para cada objeto que você inicializou dessa maneira, você deve criar e manter uma interface.

Alguém tem alguma opinião sobre isso?

Fred
fonte
Mesmo para pequenos projetos, colocar a configuração em XML torna muito mais limpo e modular - e, pela primeira vez, torna o código reutilizável (já que não está mais contaminado com cola). Para produtos de software apropriadas, você pode configurar ou permuta por exemplo, ShippingCostCalculatorou ProductUIPresentation, ou qualquer outra estratégia / implementação, dentro de exatamente a mesma base de código , através da implantação de apenas um arquivo XML alterado.
Thomas W
Se for implementado à vontade, como prática geral, acho que você adiciona mais complexidade, não menos. Quando você precisa trocar algo em tempo de execução, eles são ótimos, mas por que adicionar a ofuscação? Por que manter a sobrecarga das interfaces extras para coisas que nunca serão trocadas? Não estou dizendo que não use a IoC para testar. Por todos os meios, use a propriedade ou mesmo a injeção de dependência do construtor. Você precisa usar uma interface? Por que não subclassificar seus objetos de teste? Isso não seria mais limpo?
Fred
Você não precisa de interfaces para fazer a configuração XML / IoC. Encontrei grandes melhorias na clareza em um projeto pequeno, com apenas ~ 8 páginas e 5 ou 6 serviços. Há menos ofuscação, já que os serviços e controladores não precisam mais de cola.
Thomas W
1

Você precisaria de um contêiner de IoC se precisasse centralizar a configuração de suas dependências para que elas possam ser facilmente trocadas em massa. Isso faz mais sentido no TDD, onde muitas dependências são trocadas, mas onde há pouca interdependência entre as dependências. Isso é feito com o custo de ofuscar o fluxo de controle da construção de objetos em algum grau, portanto, é importante ter uma configuração bem organizada e razoavelmente documentada. Também é bom ter uma razão para fazer isso, caso contrário, é mera abstração . Vi isso tão mal que foi arrastado para ser o equivalente a uma declaração goto para construtores.

DaveMorganTexas
fonte
1

Aqui está o porquê. O projeto é chamado de COI com Ninject. Você pode baixar e executá-lo com o Visual Studio. Este exemplo usa o Ninject, mas TODAS as instruções 'new' estão em um local e você pode alterar completamente como o aplicativo é executado, alterando o módulo de ligação a ser usado. O exemplo é configurado para que você possa vincular a uma versão simulada dos serviços ou à versão real. Em pequenos projetos que podem não importar, mas em grandes projetos é um grande negócio.

Só para deixar claro, vantagens como as vejo: 1) TODAS as novas instruções em um local na raiz do código. 2) Re-fatore totalmente o código com uma alteração. 3) Pontos extras para 'fator legal' porque é ... bem: legal. : p

CodeChops
fonte
1

Vou tentar descobrir por que o COI pode não ser bom da minha perspectiva.

Como em todo o resto, o contêiner do IOC (ou como Einstein o colocaria I = OC ^ 2) é um conceito que você deve decidir por si mesmo se precisa ou não no seu código. Os protestos recentes sobre o COI são apenas isso, moda. Não caia na moda, isso é o primeiro. Existem inúmeros conceitos por aí que você pode implementar em seu código. Antes de tudo, estou usando a injeção de dependência desde que comecei a programar e aprendi o próprio termo quando foi popularizado com esse nome. O controle de dependência é um assunto muito antigo e foi abordado até agora em trilhões de maneiras, dependendo do que estava se dissociando do que. Separar tudo de tudo é um absurdo. O problema com o contêiner IOC é que ele tenta ser tão útil quanto o Entity Framework ou o NHibernate. Embora escrever um mapeador objeto-relacional seja simplesmente necessário, assim que você tiver que acoplar qualquer banco de dados ao seu sistema, o contêiner IOC nem sempre é necessário. Portanto, quando o contêiner IOC é útil:

  1. Quando você tem uma situação com muitas dependências, deseja organizar
  2. Quando você não se preocupa em associar seu código a produtos de terceiros
  3. Quando seus desenvolvedores querem aprender a trabalhar com uma nova ferramenta

1: Não é tão frequente que você tenha tantas dependências em seu código ou que esteja ciente delas no início do design. O pensamento abstrato é útil quando o pensamento abstrato é devido.

2: Acoplar seu código a um código de terceiros é um problema do HuGe. Eu estava trabalhando com código com mais de 10 anos de idade e que seguia os conceitos sofisticados e avançados ATL, COM, COM + e assim por diante. Não há nada que você possa fazer com esse código agora. O que estou dizendo é que um conceito avançado oferece uma vantagem aparente, mas isso é cancelado a longo prazo com a vantagem desatualizada. Isso havia tornado tudo mais caro.

3: O desenvolvimento de software é bastante difícil. Você pode estendê-lo a níveis irreconhecíveis se permitir que algum conceito avançado seja cortado em seu código. Há um problema com o IOC2. Embora esteja dissociando dependências, está dissociando também o fluxo lógico. Imagine que você encontrou um bug e precisa fazer uma pausa para examinar a situação. O COI2, como qualquer outro conceito avançado, está tornando isso mais difícil. Corrigir um bug dentro de um conceito é mais difícil do que corrigir um bug em um código mais simples, porque quando você corrige um bug, um conceito deve ser obedecido novamente. (Apenas para dar um exemplo, o C ++ .NET está constantemente alterando a sintaxe tanto que você precisa pensar bastante antes de refatorar uma versão mais antiga do .NET.) Então, qual é o problema com o IOC? O problema está na resolução de dependências. A lógica para resolver geralmente está oculta no IOC2, escrito talvez de maneira incomum que você precise aprender e manter. O seu produto de terceiros estará lá em 5 anos? Microsoft não era.

A síndrome "nós sabemos como" está escrita em todo o lugar em relação à IOC2. Isso é semelhante ao teste de automação. Termo extravagante e solução perfeita à primeira vista, basta colocar todos os seus testes para executar durante a noite e ver os resultados pela manhã. É realmente doloroso explicar empresa após empresa o que realmente significa testes automatizados. O teste automatizado definitivamente não é uma maneira rápida de reduzir o número de bugs que você pode introduzir durante a noite para aumentar a qualidade do seu produto. Mas, a moda está tornando essa noção irritantemente dominante. IOC2 sofre a mesma síndrome. Acredita-se que você precise implementá-lo para que seu software seja bom. Toda entrevista recente me perguntaram se estou implementando o IOC2 e a automação. Isso é um sinal de moda: a empresa tinha uma parte do código escrita no MFC que não abandonaria.

Você precisa aprender IOC2 como qualquer outro conceito em software. A decisão se o IOC2 precisar ser usado é da equipe e da empresa. No entanto, pelo menos TODOS os argumentos acima devem ser mencionados antes que a decisão seja tomada. Somente se você vir que o lado positivo supera o lado negativo, poderá tomar uma decisão positiva.

Não há nada de errado com o IOC2, exceto que ele resolve apenas os problemas que resolve e apresenta os problemas que apresenta. Nada mais. No entanto, ir contra a moda é muito difícil, eles têm boca suada, os seguidores de qualquer coisa. É estranho como nenhum deles existe quando o problema com sua fantasia se torna aparente. Muitos conceitos na indústria de software foram defendidos porque geram lucro, livros são escritos, conferências são realizadas, novos produtos são feitos. Isso é moda, geralmente de curta duração. Assim que as pessoas encontram outra coisa, elas a abandonam completamente. O IOC2 é útil, mas mostra os mesmos sinais que muitos outros conceitos desaparecidos que já vi. Não sei se vai sobreviver. Não há regra para isso. Você acha que se for útil, sobreviverá. Não, não é assim. Uma grande empresa rica é suficiente e o conceito pode morrer dentro de algumas semanas. Veremos. O NHibernate sobreviveu, a EF ficou em segundo lugar. Talvez o IOC2 também sobreviva. Não esqueça que a maioria dos conceitos em desenvolvimento de software não tem nada de especial, é muito lógica, simples e óbvia e, às vezes, é mais difícil lembrar a convenção de nomenclatura atual do que entender o próprio conceito. O conhecimento do IOC2 torna um desenvolvedor um desenvolvedor melhor? Não, porque se um desenvolvedor não conseguiu criar um conceito de natureza semelhante ao IOC2, será difícil para ele entender qual problema o IOC2 está resolvendo, usá-lo parecerá artificial e ele poderá começar a usá-lo. por uma questão de ser algum tipo de politicamente correto. Não esqueça que a maioria dos conceitos em desenvolvimento de software não tem nada de especial, é muito lógica, simples e óbvia e, às vezes, é mais difícil lembrar a convenção de nomenclatura atual do que entender o próprio conceito. O conhecimento do IOC2 torna um desenvolvedor um desenvolvedor melhor? Não, porque se um desenvolvedor não conseguiu criar um conceito de natureza semelhante ao IOC2, será difícil para ele entender qual problema o IOC2 está resolvendo, usá-lo parecerá artificial e ele poderá começar a usá-lo. por uma questão de ser algum tipo de politicamente correto. Não esqueça que a maioria dos conceitos em desenvolvimento de software não tem nada de especial, é muito lógica, simples e óbvia e, às vezes, é mais difícil lembrar a convenção de nomenclatura atual do que entender o próprio conceito. O conhecimento do IOC2 torna um desenvolvedor um desenvolvedor melhor? Não, porque se um desenvolvedor não conseguiu criar um conceito de natureza semelhante ao IOC2, será difícil para ele entender qual problema o IOC2 está resolvendo, usá-lo parecerá artificial e ele poderá começar a usá-lo. por uma questão de ser algum tipo de politicamente correto. O conhecimento do IOC2 torna um desenvolvedor um desenvolvedor melhor? Não, porque se um desenvolvedor não conseguiu criar um conceito de natureza semelhante ao IOC2, será difícil para ele entender qual problema o IOC2 está resolvendo, usá-lo parecerá artificial e ele poderá começar a usá-lo. por uma questão de ser algum tipo de politicamente correto. O conhecimento do IOC2 torna um desenvolvedor um desenvolvedor melhor? Não, porque se um desenvolvedor não conseguiu criar um conceito de natureza semelhante ao IOC2, será difícil para ele entender qual problema o IOC2 está resolvendo, usá-lo parecerá artificial e ele poderá começar a usá-lo. por uma questão de ser algum tipo de politicamente correto.

user1228608
fonte
0

Pessoalmente, eu uso a IoC como algum tipo de mapa de estrutura do meu aplicativo (Sim, eu também prefiro StructureMap;)). Isso facilita a substituição de minhas implementações de interface usual pelas implementações de Moq durante os testes. Criar uma configuração de teste pode ser tão fácil quanto fazer uma nova chamada de inicialização para minha estrutura de IoC, substituindo qualquer classe que seja minha fronteira de teste por uma simulação.

Provavelmente não é para isso que a IoC existe, mas é para isso que eu me uso mais.

cwap
fonte
0

Percebo que este é um post bastante antigo, mas parece estar razoavelmente ativo, e pensei em contribuir com alguns pontos que ainda não foram mencionados em outras respostas.

Eu direi que concordo com os benefícios da injeção de dependência, mas prefiro construir e gerenciar os objetos pessoalmente, usando um padrão não muito diferente do descrito por Maxm007 em sua resposta. Encontrei dois problemas principais ao usar contêineres de terceiros:

1) Ter uma biblioteca de terceiros gerenciando o tempo de vida de seus objetos "automagicamente" pode se prestar a resultados inesperados. Descobrimos que, especialmente em grandes projetos, é possível ter muito mais cópias de um objeto do que o esperado e mais do que você teria se estivesse gerenciando manualmente os ciclos de vida. Tenho certeza de que isso varia dependendo da estrutura usada, mas o problema existe. Isso também pode ser problemático se o objeto contiver recursos, conexões de dados etc., pois o objeto pode durar mais do que o esperado. Tão inevitavelmente, os contêineres de IoC tendem a aumentar a utilização de recursos e a pegada de memória de um aplicativo.

2) Os contêineres IoC, na minha opinião, são uma forma de "programação de caixa preta". Eu descobri que, em particular, nossos desenvolvedores menos experientes tendem a abusar deles. Ele permite que o programador não precise pensar em como os objetos devem se relacionar ou como desacoplar, porque fornece a eles um mecanismo no qual eles podem simplesmente pegar qualquer objeto que quiserem do nada. Por exemplo, pode haver um bom motivo de design para que o ObjectA nunca deva saber diretamente sobre o ObjectB, mas, em vez de criar uma fábrica, ponte ou localizador de serviço, um programador inexperiente simplesmente dirá "sem problemas, eu apenas pegarei o ObjectB no contêiner IoC " Na verdade, isso pode levar ao aumento do acoplamento de objetos, que é o que a IoC deve ajudar a impedir.

amnésia
fonte
-8

A injeção de dependência em um projeto ASP.NET pode ser realizada com algumas linhas de código. Suponho que haja alguma vantagem em usar um contêiner quando você tiver um aplicativo que use vários front-ends e precise de testes de unidade.

etechpartner
fonte
1
Você pode dar um exemplo? Qual a diferença entre DI e ASP.NET em comparação com qualquer outro tipo de aplicativo?
Tofi9