Injeção de dependência versus métodos estáticos

21

Hoje tive uma discussão interessante com outro desenvolvedor sobre como abordar uma classe com um método que aceita uma string e gera uma string.

Imagine algo como o seguinte, que é completamente composto com o propósito de exemplo

public string GetStringPart(string input)
{ 
   //Some input validation which is removed for clarity

   if(input.Length > 5)
        return input.Substring(0,1);

   if(input.Substring(0,1) == "B")
        return input.Substring(0,3);

   return string.empty;
}

Uma função que possui alguma lógica baseada em sua entrada de string é adicionada a um projeto usando DI e tem um Contêiner DI instalado. Você adicionaria essa nova classe com uma interface e a injetaria onde necessário, ou a tornaria uma classe estática? Quais são os prós e os contras de cada um? Por que você (ou não) deseja fazer disso algo usado com injeção de construtor, e não apenas acessado quando necessário em qualquer lugar.

James
fonte
1
@ Ewan Para métodos auxiliares que não têm uso para abstração. Exemplo: Apache FileUtils.
Walfrat
3
@ Ewan: métodos estáticos sem efeitos colaterais são o melhor tipo de métodos, porque são simples de entender e simples de testar.
precisa saber é o seguinte
1
mas eles tornam impossível coisas teste de unidade que dependem deles
Ewan
3
@ Ewan Eh, na verdade não. Math.Max ​​() é ruim porque é estático? Se você testar seu método estático e ele estiver funcionando, poderá usá-lo com segurança nos outros métodos sem problemas. Se a estática estiver falhando, seu teste a capturará.
T. Sar - Restabelece Monica
1
se 'estático' garantisse nenhum efeito colateral, seria possível ver o argumento. mas isso não acontece.
Ewan

Respostas:

25

Não há razão para que isso precise ser injetado. Esta é apenas uma função, não possui dependências, então chame-a. Pode até ser estático, se você quiser que pareça puro. Pode-se escrever testes de unidade contra isso sem dificuldade. Se for usado em outras classes, os testes de unidade ainda poderão ser gravados.

Não há necessidade de abstrair funções sem dependências, é um exagero.

Se isso se tornar mais complexo, talvez seja necessário transmitir uma interface para um construtor ou método. Mas eu não seguiria esse caminho a menos que tivesse GetStringPartlógica complexa com base na localização etc.

Jon Raynor
fonte
2
Essa é a resposta correta! Para pior, a resposta do culto à carga foi aceita.
JacquesB
3
que a função não tem dependências não é o ponto. o problema é quando outras coisas dependem da função e tornar-se intimamente ligado a ele
Ewan
2
E se a função mudar em 6 meses e não for tão pura, mas já for usada em muitos lugares. Não é extensível como estática e também não é algo que possa ser isolado do consumo de testes de unidade de classes. Se houver uma pequena chance de mudança, eu injetaria. Dito isto, estou aberto a ouvir argumentos opostos, esse é o ponto deste post. Quais são as desvantagens dessa injeção e os aspectos positivos de uma estática?
James James
1
De um modo geral, as pessoas que discutem a injeção de dependência neste site não precisam de injeção de dependência. Daí a tendência para complexidade desnecessária.
precisa
2
@ James Se a função se torna menos pura, você está fazendo algo errado e a função provavelmente não foi bem definida desde o início. O cálculo da área de um quadrado não deve repentinamente precisar de uma chamada de banco de dados ou implantar uma atualização visual para a interface do usuário. Você pode imaginar um cenário em que um método de "Substring" de repente se torne impuro?
T. Sar - Restabelece Monica
12

Aqui está o porquê

class DOSClient {
    OrderParser orderParser;
    string orderCode;

    DOSClient(OrderParser orderParser, string ordercode) { 
        this.orderParser = orderParser; 
        this.ordercode = ordercode;
    }
    void DisplayOrderCode() {
        Console.Write( "Prefix: " + orderParser.GetStringPart(ordercode) ); 
        ...
    }
}

class GUIClient {
    OrderParser orderParser;
    string orderCode;
    GUI gui;

    GUIClient(OrderParser orderParser, string ordercode, GUI gui) { 
        this.orderParser = orderParser; 
        this.ordercode = ordercode;
        this.gui = gui;
    }

    void DisplayOrderCode() {
        gui.Prefix( orderParser.GetStringPart(ordercode) ); 
        ...
    }
}

 

class OrderParserUS : IOrderParser {

    public string GetStringPart(string input)
    { 
        //Some input validation which is removed for clarity

        if(input.Length > 5)
            return input.Substring(0,1);

        if(input.Substring(0,1) == "B")
            return input.Substring(0,3);

        return string.empty;
    }
}

class OrderParserEU : IOrderParser {

    public string GetStringPart(string input)
    { 
        //Some input validation which is removed for clarity

        if(input.Length > 6)
            return input.Substring(0,1);

        if(input.Substring(0,1) == "#")
            return input.Substring(0,3);

        return string.empty;
    }
}

Se você tivesse adotado um método estático, não haveria maneira de alterar o comportamento GetStringPartsem destruir o antigo comportamento ou poluí-lo com lógica condicional. É verdade que as estáticas são máscaras globais disfarçadas, mas o fato de desabilitarem o polimorfismo é minha principal reclamação sobre elas. Métodos estáticos não são de primeira classe em idiomas OOP. Ao dar ao método um objeto para morar, mesmo um sem estado, tornamos o método portátil. Seu comportamento pode ser transmitido como o valor de uma variável.

Aqui eu imaginei um sistema que precisa se comportar de maneira um pouco diferente quando implantado na Europa e depois nos EUA. Em vez de forçar um sistema a conter o código necessário apenas pelo outro, podemos alterar o comportamento controlando qual objeto de análise de ordem é injetado nos clientes. Isso nos permite conter a propagação dos detalhes da região. Também facilita a adição do OrderParserCanada sem a necessidade de tocar nos analisadores existentes.

Se isso não significa nada para você, então realmente não há um bom argumento para isso.

BTW, GetStringPart é um nome terrível.

candied_orange
fonte
* Cringe no estilo do código entre colchetes * Suponho que você seja um programador Java tentando escrever código C #? É preciso conhecer um, suponho. :)
Neil
A questão não é específica para um idioma, mas mais sobre design. Meu problema com os métodos estáticos é que eles impedem o isolamento do código para teste. O desenvolvedor com quem estava conversando acha que confio demais no DI e, às vezes, métodos de extensão / métodos estáticos são relevantes. Eu acho que possivelmente em casos raros, mas não como propósito geral. Discussões em andamento que eu senti que eram boas para discutir mais. Também concordo com o seu caso de polimorfismo.
James
O teste é uma razão, mas há mais. Não gosto de culpar muito os testes porque isso apenas faz as pessoas criticarem os testes. O simples fato é que os programadores procedimentais não gostam se você não programar procedimentalmente.
candied_orange
Você não poderia simplesmente dizer static getStringPartEU()? Seu exemplo só faz sentido se houver outros métodos nessa classe que também exijam o tratamento especializado da UE e eles precisem ser tratados como uma única unidade.
9788 Robert Harvey
2
Você definitivamente está argumentando a favor de complexidade desnecessária. Tudo pode ter mais e mais código adicionado a ele. O que precisa mudar deve ser fácil de modificar, mas você não deve tentar prever o que deve mudar no futuro. Os campos estáticos podem ser idênticos às variáveis ​​globais, mas os métodos estáticos podem ser puros, o melhor tipo de método possível.
precisa