Eu recebo injeção de dependência, mas alguém pode me ajudar a entender a necessidade de um contêiner de IoC?

15

Peço desculpas se isso parece mais uma repetição da pergunta, mas toda vez que encontro um artigo sobre o assunto, ele simplesmente fala sobre o que é DI. Então, eu recebo DI, mas estou tentando entender a necessidade de um contêiner IoC, no qual todo mundo parece estar se metendo. É o ponto de um contêiner IoC realmente apenas para "auto-resolver" a implementação concreta das dependências? Talvez minhas aulas não tenham várias dependências e talvez seja por isso que não vejo grande importância, mas quero ter certeza de que estou entendendo o utilitário do contêiner corretamente.

Eu normalmente divido minha lógica de negócios em uma classe que pode se parecer com isso:

public class SomeBusinessOperation
{
    private readonly IDataRepository _repository;

    public SomeBusinessOperation(IDataRespository repository = null)
    {
        _repository = repository ?? new ConcreteRepository();
    }

    public SomeType Run(SomeRequestType request)
    {
        // do work...
        var results = _repository.GetThings(request);

        return results;
    }
}

Por isso, tem apenas uma dependência e, em alguns casos, pode ter uma segunda ou terceira, mas nem sempre isso. Então, qualquer coisa que chame isso pode passar por seu próprio repositório ou permitir que ele use o repositório padrão.

No que diz respeito à minha compreensão atual de um contêiner IoC, tudo que o contêiner faz é resolver IDataRepository. Mas se isso é tudo o que faz, então eu não estou vendo uma tonelada de valor, já que minhas classes operacionais já definem um fallback quando nenhuma dependência passou. Então o único outro benefício que posso pensar é que se eu tiver várias operações como este uso o mesmo repo fallback, eu posso mudar o repo em um lugar que é o registro / fábrica / container. E isso é ótimo, mas é isso?

    
por Sinaesthetic 19.09.2014 / 01:49
fonte

3 respostas

2

O contêiner IoC não é sobre o caso em que você tem uma dependência. É sobre o caso em que você tem 3 dependências e elas têm várias dependências com dependências, etc.

Ele também ajuda você a centralizar a resolução de uma dependência e o gerenciamento do ciclo de vida das dependências.

    
por 20.09.2014 / 01:07
fonte
10

Existem vários motivos pelos quais você pode querer usar um contêiner IoC.

DLLs não referenciadas

Você pode usar um contêiner IoC para resolver uma classe concreta de uma dll não referenciada. Isso significa que você pode assumir dependências inteiramente da abstração - ou seja, a interface.

Evite o uso de new

Um contêiner IoC significa que você pode remover completamente o uso da palavra-chave new para criar uma classe. Isso tem dois efeitos. A primeira é que isso dissocia suas classes. O segundo (que está relacionado) é que você pode deixar cair em zombarias para testes unitários. Isso é incrivelmente útil, especialmente quando você está interagindo com um processo de longa duração.

Escrever contra abstrações

Usar um contêiner IoC para resolver suas dependências concretas permite que você escreva seu código em abstrações, em vez de implementar todas as classes concretas de que você precisa, conforme necessário. Por exemplo, você pode precisar do seu código para ler dados de um banco de dados. Em vez de escrever a classe de interação do banco de dados, você simplesmente escreve uma interface para ela e codifica isso. Você pode usar uma simulação para testar a funcionalidade do código que está desenvolvendo enquanto o desenvolve, em vez de depender do desenvolvimento da classe de interação do banco de dados concreta antes de poder testar o outro código.

Evite códigos frágeis

Outro motivo para usar um contêiner IoC é que, confiando no contêiner IoC para resolver suas dependências, você evita a necessidade de alterar cada chamada individual para um construtor de classe ao adicionar ou remover uma dependência. O contêiner IoC resolverá automaticamente suas dependências. Isso não é um grande problema quando você está criando uma classe uma vez, mas é um problema gigantesco quando você está criando a turma em centenas de lugares.

Gerenciamento vitalício e limpeza de recursos não gerenciados

A última razão que mencionarei é o gerenciamento das vidas úteis dos objetos. Os contêineres IoC geralmente fornecem a capacidade de especificar a vida útil de um objeto. Faz muito sentido especificar o tempo de vida de um objeto em um contêiner IoC em vez de tentar gerenciá-lo manualmente no código. O gerenciamento manual da vida pode ser muito difícil. Isso pode ser útil ao lidar com objetos que requerem descarte. Em vez de gerenciar manualmente o descarte de seus objetos, alguns contêineres IoC gerenciarão o descarte para você, o que pode ajudar a evitar vazamentos de memória e simplificar sua base de código.

O problema com o código de exemplo que você forneceu é que a classe que você está escrevendo tem uma dependência concreta na classe ConcreteRepository. Um contêiner IoC removeria essa dependência.

    
por 19.09.2014 / 02:31
fonte
2

De acordo com o princípio da responsabilidade única, toda turma deve ter apenas uma única responsabilidade. Criar novas instâncias de classes é apenas outra responsabilidade, portanto, você precisa encapsular esse tipo de código em uma ou mais classes. Você pode fazer isso usando qualquer padrão de criação, por exemplo, fábricas, construtores, contêineres DI, etc.

Existem outros princípios como inversão de controle e inversão de dependência. Nesse contexto, eles estão relacionados à instanciação de dependências. Eles afirmam que as classes de alto nível devem ser dissociadas das classes de baixo nível (dependências) que eles usam. Podemos separar as coisas criando interfaces. Portanto, as classes de baixo nível precisam implementar interfaces específicas e as classes de alto nível precisam utilizar instâncias de classes que implementam essas interfaces. (nota: Restrição de interface uniforme REST aplica a mesma abordagem em um nível de sistema.)

A combinação desses princípios pelo exemplo (desculpe pelo código de baixa qualidade, usei uma linguagem ad-hoc em vez de C #, já que não sei disso):

  1. Sem SRP, sem IoC

    class SomeHighLevelService
    {
        public doFooBar(){
            Crap crap = doFoo();
            doBar(crap);
        }
    
        public Crap doFoo(){
            //...
            return crap;
        }
    
        public doBar(Crap crap){
            //...
        }
    }
    
    SomeHighLevelService service = new SomeHighLevelService();
    service.doFooBar();
    
  2. Mais perto do SRP, sem IoC

    class SomeHighLevelService
    {
        public SomeHighLevelService(){
            Foo foo = new Foo();
            Bar bar = new Bar();
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    }
    
    class Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    SomeHighLevelService service = new SomeHighLevelService();
    service.doFooBar();
    
  3. Sim, SRP, sem IoC

    class HighLevelServiceProvider {
        public SomeHighLevelService getSomeHighLevelService(){
            SomeHighLevelService service = new SomeHighLevelService();
            service.setFoo(this.getFoo());
            service.getBar(this.getBar());
            return service;
        }
    
        private Foo getFoo(){
            return new Foo();
        }
    
        private Bar getBar(){
            return new Bar();
        }
    }
    
    class SomeHighLevelService
    {           
        public setFoo(Foo foo){
            this.foo = foo;
        }
    
        public setBar(Bar bar){
            this.bar = bar;
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    
    }
    
    class Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    HighLevelServiceProvider provider = new HighLevelServiceProvider();
    SomeHighLevelService service = provider.getSomeHighLevelService();
    service.doFooBar();
    
  4. Sim, SRP, sim, IoC

    interface HighLevelServiceProvider {
        SomeHighLevelService getSomeHighLevelService();
    }
    
    interface SomeHighLevelService {
        doFooBar();
    }
    
    interface Foo {
        Crap doFoo();
    }
    
    interface Bar {
        doBar(Crap crap);
    }
    
    
    class ConcreteHighLevelServiceContainer implements HighLevelServiceProvider {
        public SomeHighLevelService getSomeHighLevelService(){
            SomeHighLevelService service = new ConcreteHighLevelService();
            service.setFoo(this.getFoo());
            service.getBar(this.getBar());
            return service;
        }
    
        private Foo getFoo(){
            return new ConcreteFoo();
        }
    
        private Bar getBar(){
            return new ConcreteBar();
        }
    }
    
    class ConcreteHighLevelService implements SomeHighLevelService
    {           
        public setFoo(Foo foo){
            this.foo = foo;
        }
    
        public setBar(Bar bar){
            this.bar = bar;
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    
    }
    
    class ConcreteFoo implements Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class ConcreteBar implements Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    
    HighLevelServiceProvider provider = new ConcreteHighLevelServiceContainer();
    SomeHighLevelService service = provider.getSomeHighLevelService();
    service.doFooBar();
    

Então acabamos tendo um código no qual você pode substituir cada implementação concreta por outra que implementa a mesma interface ofc. Então isso é bom porque as classes participantes são dissociadas umas das outras, elas conhecem apenas as interfaces. Outra vantagem que o código da instanciação é reutilizável.

    
por 19.09.2014 / 04:38
fonte