Me venda em contêineres IoC, por favor

17

Eu já vi vários recomendar o uso de contêineres IoC no código. A motivação é simples. Pegue o seguinte código injetado de dependência:

class UnitUnderTest
{
    std::auto_ptr<Dependency> d_;
public:
    UnitUnderTest(
        std::auto_ptr<Dependency> d = std::auto_ptr<Dependency>(new ConcreteDependency)
    ) : d_(d)
    {
    }
};


TEST(UnitUnderTest, Example)
{
    std::auto_ptr<Dependency> dep(new MockDependency);
    UnitUnderTest uut(dep);
    //Test here
}

Para dentro:

class UnitUnderTest
{
    std::auto_ptr<Dependency> d_;
public:
    UnitUnderTest()
    {
        d_.reset(static_cast<Dependency *>(IocContainer::Get("Dependency")));
    }
};


TEST(UnitUnderTest, Example)
{
    UnitUnderTest uut;
    //Test here
}

//Config for IOC container normally
<Dependency>ConcreteDependency</Dependency>

//Config for IOC container for testing
<Dependency>MockDependency</Dependency>

(O exemplo acima é hipotético em C ++, é claro)

Embora eu concorde que isso simplifique a interface da classe removendo o parâmetro construtor de dependência, acho que a cura é pior que a doença por alguns motivos. Primeiro, e este é um grande problema para mim, isso torna seu programa dependente de um arquivo de configuração externo. Se você precisar de implantação binária única, simplesmente não poderá usar esses tipos de contêineres. A segunda questão é que a API agora está fraca e pior, tipicamente estrita . A evidência (neste exemplo hipotético) é o argumento de string para o contêiner de IoC e o elenco no resultado.

Então .. existem outros benefícios do uso desses tipos de contêineres ou apenas discordo dos que os recomendam?

Billy ONeal
fonte
1
O código de exemplo parece um péssimo exemplo de uma implementação de IoC. Eu só estou familiarizado com C #, mas certamente há uma maneira de ter injeção de construtor e configuração programática em C ++ também?
RMAC

Respostas:

12

Em uma aplicação grande com muitas camadas e muitas partes móveis, são os inconvenientes que começam a parecer bem menores em comparação com as vantagens.

O contêiner "simplifica a interface" da classe, mas o faz de uma maneira muito importante. O contêiner é uma solução para o problema que a injeção de dependência cria, que é a necessidade de transmitir dependências por todo o lugar, através de gráficos de objetos e em áreas funcionais. Você tem um pequeno exemplo aqui que tem uma dependência - e se esse objeto tivesse três dependências e os objetos que dependessem dele tivessem vários objetos que dependessem deles , e assim por diante? Sem um contêiner, os objetos na parte superior dessas cadeias de dependência acabam se tornando responsáveis ​​por acompanhar todas as dependências em todo o aplicativo.

Existem diferentes tipos de contêineres também. Nem todos eles são digitados de forma estrita e nem todos exigem arquivos de configuração.

nlawalker
fonte
3
Mas um grande projeto faz com que os problemas já mencionados pior . Se o projeto for grande, o arquivo de configuração se torna um imã de dependência enorme e fica ainda mais fácil ter um erro de digitação levando a um comportamento indefinido no tempo de execução. (ou seja, ter um erro de digitação no arquivo de configuração faria com que o elenco causasse um comportamento indefinido se o tipo errado fosse retornado). Quanto às dependências de dependências, elas não importam aqui. Ou o construtor cuida de fazê-los ou o teste está zombando da dependência de nível superior.
Billy ONeal
... Continuação ... Quando o programa não está sendo testado, a dependência concreta padrão é instanciada a cada etapa. Em outros tipos de contêiner, você tem alguns exemplos?
Billy ONeal
4
TBH Não tenho muita experiência com implementações de DI, mas observe o MEF no .NET, que pode ser, mas não precisa ser digitado, não possui arquivo de configuração e implementações concretas de interfaces são "registradas" nas próprias aulas. Aliás, quando você diz: "o construtor cuida de fazê-los" - se é isso que você está fazendo (também conhecido como "injeção de construtor do pobre homem"), então você não precisa de um contêiner. A vantagem de um contêiner em relação a isso é que ele centraliza informações sobre todas essas dependências.
Nlawalker
+1 para esse último comentário - a última frase não é a resposta :)
Billy ONeal
1
Ótima resposta que me ajudou a entender o ponto inteiro. O ponto não é apenas simplificar o padrão, mas permitir que os elementos no topo da cadeia de dependência sejam tão ingênuos quanto os elementos na parte inferior.
22416 Christopher Berman
4

A estrutura do Guice IoC do Java não depende de um arquivo de configuração, mas do código de configuração . Isso significa que a configuração é um código diferente do código que compõe seu aplicativo real e pode ser refatorada etc.

Acredito que o Guice é o framework Java IoC que finalmente acertou na configuração.


fonte
Existem exemplos semelhantes para C ++?
Billy ONeal
@ Billy, eu não estou familiarizado o suficiente com C ++ para poder dizer.
0

Essa ótima resposta do SO de Ben Scheirman detalha alguns exemplos de código em C #; algumas vantagens dos contêineres IoC (DI) sendo:

  • Sua cadeia de dependências pode ficar aninhada e rapidamente se torna difícil conectá-las manualmente.
  • Permite o uso de programação orientada a aspectos .
  • 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).
dodgy_coder
fonte
1
Não vejo como nenhuma dessas coisas pode ser implementada com uma simples injeção de construtor.
Billy ONeal
Com um bom design, a injeção de construtor ou outro método pode ser suficiente para você; as vantagens desses contêineres IoC podem ser apenas em casos específicos. O exemplo dado com INotifyPropertyChanged em um projeto WPF foi um exemplo.
precisa saber é o seguinte