Essa é uma decisão de design que parece surgir bastante: como passar o contexto por um método que não precisa dele para um método que precisa. Existe uma resposta certa ou depende do contexto.
Código de exemplo que requer uma solução
// needs the dependency
function baz(session) {
session('baz');
}
// doesn't care about the dependency
function bar() {
baz();
}
// needs the dependency
function foo(session) {
session('foo')
bar();
}
// creates the dependency
function start() {
let session = new Session();
foo(session);
}
Soluções possíveis
- threadlocal
- global
- objeto de contexto
- passar a dependência através
- curry baz e passá-lo para a barra com a dependência definida como o primeiro argumento
- Injeção de dependência
Exemplos de onde vem
Processamento de solicitação HTTP
Objetos de contexto na forma de atributos de solicitação são frequentemente usados: consulte expressjs, Java Servlets ou o owin do .net.
Exploração madeireira
Para o registro em Java, as pessoas geralmente usam globals / singletons. Consulte os padrões log4j / commons logging / java logging típicos.
Transações
Os locais de encadeamento costumam ser usados para manter uma transação ou sessão associada a uma cadeia de chamadas de método, para evitar a necessidade de passá-las como parâmetros para todos os métodos que não precisam.
design-patterns
Jamie McCrindle
fonte
fonte
Respostas:
A única resposta justa é que depende das expressões idiomáticas do seu paradigma de programação. Se você estiver usando OO, é quase certamente incorreto passar uma dependência de método para método para método. É um cheiro de código no OO. De fato, esse é um dos problemas que o OO resolve - um objeto corrige um contexto. Portanto, no OO, uma abordagem correta (sempre há outras maneiras) é fornecer a dependência via contratante ou propriedade. Um comentarista menciona "Injeção de dependência" e isso é perfeitamente legítimo, mas não é estritamente necessário. Basta fornecer a dependência para que ela esteja disponível como membro para
foo
ebaz
.Você mencionou o curry, então eu assumirei que a programação funcional não está fora de questão. Nesse caso, um equivalente filosófico do contexto do objeto é o fechamento. Qualquer abordagem que, mais uma vez, corrija a dependência para que fique disponível para os dependentes funciona bem. O curry é uma dessas abordagens (e faz você parecer inteligente). Lembre-se de que existem outras maneiras de fechar uma dependência. Alguns deles são elegantes e outros terríveis.
Não se esqueça da programação orientada a aspectos . Parece ter caído em desuso nos últimos anos, mas seu objetivo principal é resolver exatamente o problema que você descreve. De fato, o exemplo clássico da Aspect é o log. No AOP, a dependência é adicionada automaticamente após a gravação de outro código. O pessoal da AOP chama isso de " tecelagem ". Aspectos comuns são inseridos no código nos locais apropriados. Isso torna seu código mais fácil de pensar e é bem legal, mas também adiciona uma nova carga de teste. Você precisará de uma maneira de determinar se seus artefatos finais são sólidos. AOP também tem respostas para isso, então não se sinta intimidado.
fonte
Se
bar
é dependentebaz
, o que por sua vez exigedependency
,bar
requerdependency
também para ser usado corretamentebaz
. Portanto, as abordagens corretas seriam passar a dependência como um parâmetro parabar
ou currybaz
e passar isso parabar
.A primeira abordagem é mais simples de implementar e ler, mas cria um acoplamento entre
bar
ebaz
. A segunda abordagem remove esse acoplamento, mas pode resultar em um código menos claro. Qual a melhor abordagem, portanto, provavelmente dependerá da complexidade e do comportamento de ambas as funções. Por exemplo, sebaz
oudependency
tiver efeitos colaterais, a facilidade de teste provavelmente será um grande driver para a escolha da solução.Eu sugiro que todas as outras opções que você propõe sejam de natureza "hacky" e provavelmente levem a problemas tanto nos testes quanto na dificuldade de rastrear bugs.
fonte
dependency
por parâmetros é injeção de dependência?Filosoficamente falando
Eu concordo com a preocupação de David Arno .
Estou lendo o OP procurando soluções de implementação. No entanto, a resposta é alterar o design . "Padrões"? O design de OO é, poder-se-ia dizer, tudo sobre contexto. É uma vasta folha de papel em branco cheia de possibilidades.
Lidar com o código existente é um contexto bem diferente.
Estou trabalhando no mesmo problema agora. Bem, estou consertando as centenas de linhas de código copy-n-paste que foram feitas apenas para que um valor possa ser injetado.
Modularizar o código
Joguei fora 600 linhas de código duplicado e depois refatorei, em vez de "A chama B chama C chama D ..." Eu tenho "Ligue para A, retorne, Ligue para B, retorne, Ligue para C ...". Agora só precisamos injetar o valor em um desses métodos, digamos que o método E.
Adicione um parâmetro padrão ao construtor. Os chamadores existentes não mudam - "opcional" é a palavra operacional aqui. Se um argumento não for passado, o valor padrão será usado. Em seguida, apenas 1 linha é alterada para passar a variável para a estrutura modular refatorada; e uma pequena alteração no método E para usá-lo.
Encerramentos
Um tópico de programadores - "Por que um programa usaria um fechamento?"
Essencialmente, você está injetando valores em um método que retorna um método personalizado com os valores. Esse método personalizado é executado posteriormente.
Essa técnica permitiria modificar um método existente sem alterar sua assinatura.
fonte