A zombaria introduz a manipulação no código de produção

15

Supondo uma interface IReader, uma implementação da interface IReader ReaderImplementation e uma classe ReaderConsumer que consome e processa dados do leitor.

public interface IReader
{
     object Read()
}

Implementação

public class ReaderImplementation
{
    ...
    public object Read()
    {
        ...
    }
}

Consumidor:

public class ReaderConsumer()
{
    public string location

    // constructor
    public ReaderConsumer()
    {
        ...
    }

    // read some data
    public object ReadData()
    {
        IReader reader = new ReaderImplementation(this.location)
        data = reader.Read()
        ...
        return processedData    
    }
}

Para testar o ReaderConsumer e o processamento, uso uma simulação do IReader. Então o ReaderConsumer se torna:

public class ReaderConsumer()
{
    private IReader reader = null

    public string location

    // constructor
    public ReaderConsumer()
    {
        ...
    }

    // mock constructor
    public ReaderConsumer(IReader reader)
    {
        this.reader = reader
    }

    // read some data
    public object ReadData()
    {
        try
        {
            if(this.reader == null)
            {
                 this.reader = new ReaderImplementation(this.location)
            }

            data = reader.Read()
            ...
            return processedData    
        }
        finally
        {
            this.reader = null
        }
    }
}

Nesta solução, a simulação introduz uma sentença if para o código de produção, pois apenas o construtor da simulação fornece instâncias da interface.

Ao escrever isso, percebo que o bloco try-finally não está relacionado, pois existe para lidar com o usuário alterar o local durante o tempo de execução do aplicativo.

No geral, parece mal cheiroso, como pode ser tratado melhor?

kristian mo
fonte
16
Normalmente, isso não é um problema, porque o construtor com a dependência injetada seria o único construtor. Está fora de questão se tornar ReaderConsumerindependente ReaderImplementation?
precisa saber é o seguinte
Atualmente, seria difícil remover a dependência. Ao analisar um pouco mais, tenho um problema mais profundo do que apenas a dependência da implementação do ReaderImpl. Como o ReaderConsumer é criado durante a inicialização de uma fábrica, persiste durante o tempo de vida do aplicativo e aceita alterações dos usuários, isso requer uma massagem extra. Provavelmente, a entrada de configuração / usuário pode existir como um objeto e, em seguida, ReaderConsumer e ReaderImplementation podem ser criados em tempo real. Ambas as respostas dadas resolvem muito bem o caso mais genérico.
precisa saber é o seguinte
3
Sim . Este é o ponto do TDD: ter que escrever testes primeiro implica em um design mais dissociado (caso contrário, você não poderá escrever testes de unidade ...). Isso ajuda a tornar o código mais sustentável e também extensível.
Bakuriu 23/01
Uma boa maneira de detectar odores que podem ser resolvidos com a injeção de dependência é procurar a palavra-chave 'novo'. Não atualize suas dependências. Injete-os em seu lugar.
precisa saber é o seguinte

Respostas:

67

Em vez de inicializar o leitor a partir do seu método, mova esta linha

{
    this.reader = new ReaderImplementation(this.location)
}

No construtor sem parâmetros padrão.

public ReaderConsumer()
{
    this.reader = new ReaderImplementation(this.location)
}

public ReaderConsumer(IReader reader)
{
    this.reader = reader
}

Não existe um "construtor simulado", se sua classe tem uma dependência necessária para funcionar, então o construtor deve fornecer essa coisa ou criá-la.

Pato de borracha
fonte
3
score ++ ... exceto do ponto de vista do DI, esse construtor padrão é definitivamente um cheiro de código.
Mathieu Guindon
3
@ Mat'sMug nada de errado com uma implementação padrão. A falta de encadeamento de ctor é o cheiro aqui. =;) - #
RubberDuck 23/01
Ah, sim, existe - se você não possui o livro, este artigo parece descrever muito bem o anti-padrão bastardo da injeção (apenas passou por cima).
Mathieu Guindon
3
@ Mat'sMug: Você está interpretando DI dogmaticamente. Se você não precisa injetar o construtor padrão, não precisa.
Robert Harvey
3
O livro @ Mat'sMug linked também fala sobre "padrões aceitáveis" para dependências serem boas (embora esteja se referindo à injeção de propriedades). Vamos colocar desta maneira: a abordagem de dois construtores custa mais em termos de simplicidade e manutenção do que a abordagem de um construtor e, com a questão sendo simplificada demais para maior clareza, o custo não é justificado, mas pode ser em alguns casos .
Carl Leth
54

Você só precisa do construtor único:

public class ReaderConsumer()
{
    private IReader reader = null

    public string location

    // constructor
    public ReaderConsumer(IReader reader)
    {
        this.reader = reader;
    }

no seu código de produção:

var rc = new ReaderConsumer(new ReaderImplementation(0));

no seu teste:

var rc = new ReaderConsumer(new MockImplementation(0));
Ewan
fonte
13

Examinar injeção de dependência e inversão de controle

Ewan e RubberDuck têm excelentes respostas. Mas eu queria mencionar outra área para examinar qual é Injeção de Dependência (DI) e Inversão de Controle (IoC). Ambas as abordagens movem o problema que você está enfrentando para uma estrutura / biblioteca, para que você não precise se preocupar com isso.

Seu exemplo é simples e rapidamente é dispensado, mas, inevitavelmente, você vai desenvolver isso e acabar com toneladas de construtores ou rotinas de inicialização que se parecem com:

var foo = novo Foo (novo Bar (novo Baz (), novo Quz ()), novo Foo2 ());

Com o DI / IoC, você usa uma biblioteca que permite especificar as regras para combinar interfaces com implementações e então você diz "Give me a Foo" e descobre como conectar tudo isso.

Existem muitos recipientes de IoC muito amigáveis ​​(como são chamados) por aí, e eu vou recomendar um para olhar, mas, por favor, explore, pois existem muitas opções excelentes.

Um simples para começar é:

http://www.ninject.org/

Aqui está uma lista para explorar:

http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx

Reginald Blue
fonte
7
As respostas de Ewan e RubberDuck demonstram DI. O DI / IoC não trata de uma ferramenta ou estrutura, trata-se de arquitetar e estruturar o código de forma que o controle seja invertido e as dependências sejam injetadas. O livro de Mark Seemann faz um ótimo trabalho (impressionante!) Em explicar como o DI / IoC é completamente possível sem uma estrutura de IoC e por que e quando uma estrutura de IoC se torna uma boa ideia (dica: quando você tem dependências de dependências de dependências de .. .) =)
Mathieu Guindon
Também ... +1 porque o Ninject é incrível e, ao reler, sua resposta parece boa. O primeiro parágrafo parece um pouco com as respostas de Ewan e RubberDuck, mas não são sobre DI, que foi o que motivou meu primeiro comentário.
Mathieu Guindon
1
@ Mat'sMug Definitivamente entenda seu ponto. Eu estava tentando encorajar o OP a olhar para DI / IoC que os outros pôsteres estavam, de fato, usando (o de Ewan é o DI do pobre), mas não mencionei. A vantagem, é claro, de usar uma estrutura é que ela mergulhará o usuário no mundo da DI com muitos exemplos concretos que, com sorte, os guiarão a entender o que estão fazendo ... embora ... nem sempre. :-) E, é claro, adorei o livro de Mark Seemann. É um dos meus favoritos.
Reginald Blue
Obrigado pela dica sobre um quadro DI, parece ser o caminho para refatorar essa bagunça corretamente :)
mo kristian