Recentemente, tenho lido um livro intitulado Programação Funcional em C # e me ocorre que a natureza imutável e sem estado da programação funcional obtém resultados semelhantes aos padrões de injeção de dependência e é possivelmente uma abordagem ainda melhor, especialmente em relação ao teste de unidade.
Eu ficaria grato se alguém que tenha experiência com ambas as abordagens pudesse compartilhar seus pensamentos e experiências para responder à pergunta principal: a Programação Funcional é uma alternativa viável aos padrões de injeção de dependência?
Respostas:
O gerenciamento de dependências é um grande problema no OOP pelos dois motivos a seguir:
A maioria dos programadores de OO considera o acoplamento rígido de dados e código totalmente benéfico, mas isso tem um custo. Gerenciar o fluxo de dados através das camadas é uma parte inevitável da programação em qualquer paradigma. O acoplamento de seus dados e código adiciona o problema adicional de que, se você quiser usar uma função em um determinado momento, precisará encontrar uma maneira de levar seu objeto a esse ponto.
O uso de efeitos colaterais cria dificuldades semelhantes. Se você usa um efeito colateral para algumas funcionalidades, mas deseja poder trocar sua implementação, praticamente não há outra opção a não ser injetar essa dependência.
Considere como exemplo um programa de spammer que rastreia páginas da Web em busca de endereços de e-mail e as envia por e-mail. Se você tem uma mentalidade de DI, agora está pensando nos serviços que irá encapsular atrás das interfaces e quais serviços serão injetados onde. Vou deixar esse design como um exercício para o leitor. Se você tem uma mentalidade de FP, agora está pensando nas entradas e saídas da camada mais baixa de funções, como:
Quando você pensa em termos de entradas e saídas, não há dependências de funções, apenas dependências de dados. É isso que os torna tão fáceis de realizar testes unitários. Sua próxima camada organiza a saída de uma função para ser inserida na entrada da próxima e pode facilmente trocar as várias implementações, conforme necessário.
Em um sentido muito real, a programação funcional naturalmente o incentiva a sempre inverter suas dependências de funções e, portanto, você normalmente não precisa tomar nenhuma medida especial para fazê-lo após o fato. Quando você faz isso, ferramentas como funções de ordem superior, fechamentos e aplicativos parciais facilitam a realização com menos clichês.
Observe que não são as próprias dependências que são problemáticas. São as dependências que apontam para o lado errado. A próxima camada acima pode ter uma função como:
É perfeitamente aceitável que essa camada tenha dependências codificadas dessa maneira, porque seu único objetivo é colar as funções da camada inferior. Trocar uma implementação é tão simples quanto criar uma composição diferente:
Essa recomposição fácil é possível devido à falta de efeitos colaterais. As funções da camada inferior são completamente independentes uma da outra. A próxima camada acima pode escolher qual
processText
é realmente usada com base em algumas configurações do usuário:Novamente, não é um problema, porque todas as dependências apontam para um caminho. Não precisamos inverter algumas dependências para que todas apontem da mesma maneira, porque funções puras já nos forçaram a fazê-lo.
Observe que você pode tornar isso muito mais acoplado passando
config
para a camada mais baixa, em vez de verificá-la na parte superior. O FP não impede que você faça isso, mas tende a torná-lo muito mais irritante se você tentar.fonte
System.String
. Um sistema de módulos permite substituirSystem.String
por uma variável, para que a escolha da implementação da cadeia não seja codificada, mas ainda seja resolvida no momento da compilação.Isso me parece uma pergunta estranha. As abordagens de programação funcional são em grande parte tangenciais à injeção de dependência.
Certamente, ter um estado imutável pode fazer com que você não "trapaceie" tendo efeitos colaterais ou usando o estado de classe como um contrato implícito entre funções. Isso torna a passagem de dados mais explícita, o que suponho ser a forma mais básica de injeção de dependência. E o conceito de programação funcional de passar funções facilita muito isso.
Mas isso não remove dependências. Suas operações ainda precisam de todos os dados / operações de que precisavam quando seu estado era mutável. E você ainda precisa obter essas dependências de alguma forma. Portanto, eu não diria que as abordagens de programação funcional substituem a DI, portanto, não há nenhum tipo de alternativa.
Na verdade, eles acabaram de mostrar o quão ruim o código OO pode criar dependências implícitas do que os programadores raramente pensam.
fonte
A resposta rápida para sua pergunta é: Não .
Mas, como outros afirmaram, a questão se casa com dois conceitos, um pouco não relacionados.
Vamos fazer isso passo a passo.
DI resulta em estilo não funcional
No núcleo da programação de funções, existem funções puras - funções que mapeiam a entrada para a saída, para que você sempre obtenha a mesma saída para uma determinada entrada.
O DI normalmente significa que sua unidade não é mais pura, pois a saída pode variar dependendo da injeção. Por exemplo, na seguinte função:
getBookedSeatCount
(uma função) pode variar, produzindo resultados diferentes para a mesma entrada. Isto fazbookSeats
impuro.Há exceções para isso - você pode injetar um dos dois algoritmos de classificação que implementam o mesmo mapeamento de entrada e saída, embora usando algoritmos diferentes. Mas essas são exceções.
Um sistema não pode ser puro
O fato de um sistema não poder ser puro é igualmente ignorado, pois é afirmado em fontes de programação funcional.
Um sistema deve ter efeitos colaterais, com os exemplos óbvios sendo:
Portanto, parte do seu sistema deve envolver efeitos colaterais e essa parte também pode envolver estilo imperativo ou estilo OO.
O paradigma do núcleo da casca
Tomando emprestado os termos da excelente conversa de Gary Bernhardt sobre limites , uma boa arquitetura de sistema (ou módulo) incluirá essas duas camadas:
O principal argumento é "dividir" o sistema em sua parte pura (o núcleo) e a parte impura (a concha).
Embora ofereça uma solução (e conclusão) levemente falha, este artigo de Mark Seemann propõe o mesmo conceito. A implementação do Haskell é particularmente interessante, pois mostra que tudo pode ser feito usando o FP.
DI e FP
O emprego da DI é perfeitamente razoável, mesmo que a maior parte do seu aplicativo seja pura. A chave é confinar o DI dentro da concha impura.
Um exemplo serão os stubs da API - você deseja a API real em produção, mas usa stubs nos testes. A adesão ao modelo do núcleo da casca ajudará bastante aqui.
Conclusão
Então FP e DI não são exatamente alternativas. É provável que você tenha ambos no seu sistema, e o conselho é garantir a separação entre a parte pura e impura do sistema, onde FP e DI residem respectivamente.
fonte
Do ponto de vista da OOP, as funções podem ser consideradas interfaces de método único.
Interface é um contrato mais forte que uma função.
Se você estiver usando uma abordagem funcional e fizer muita DI, em comparação ao uso de uma abordagem OOP, obterá mais candidatos para cada dependência.
vs
fonte