Padrões para passar o contexto por uma cadeia de métodos

19

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.

Jamie McCrindle
fonte
Por favor, use um exemplo mais significativo.
Tulains Córdova
Adicionei alguns exemplos de onde isso ocorre.
Jamie McCrindle
3
Eu quis dizer um código de exemplo mais significativo.
Tulains Córdova

Respostas:

11

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 fooe baz.

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.

Roger escasso
fonte
Afirmar que a passagem de parâmetros nos métodos OO é um cheiro de código é uma afirmação altamente controversa. Eu argumentaria o contrário: encorajar a mistura de estado e funcionalidade dentro de uma classe é um dos maiores erros cometidos pelo paradigma OO e evitá-lo injetando dependências diretamente nos métodos, em vez de via construtor, é um sinal de uma peça bem projetada. de código, seja OO ou não.
David Arno
3
@DavidArno Eu sugeriria que usar um paradigma diferente versus concluir o estado do objeto é "um dos maiores erros cometidos pelo paradigma OO" e depois contornar o paradigma. Não tenho nada contra quase qualquer abordagem, mas geralmente não gosto de código em que o autor está lutando com sua ferramenta. O estado privado é um recurso distintivo do OO. Se você evita esse recurso, perde parte do poder do OO.
perfil completo
1
@DavidArno Uma classe que é todo estado e sem funcionalidade não tem mecanismo para impor relacionamentos invariantes no estado. Essa classe não é absolutamente nenhuma OO.
Kevin Krumwiede
@KevinKrumwiede, até certo ponto, você aplicou reducto ad absudium ao meu comentário, mas seu argumento ainda está bem fundamentado. A invariância no estado é uma parte importante de "passar do OO". Portanto, evitar a mistura de funcionalidade e estado deve permitir funcionalidade suficiente em um objeto de estado, conforme necessário, para obter invariância (campos encapsulados definidos pelo construtor e acessados ​​via getters).
David Arno
@ ScantRoger, concordo que outro paradigma possa ser adotado, o paradigma funcional. Curiosamente, a maioria das linguagens "OO" modernas tem uma lista crescente de recursos funcionais e, portanto, é possível ficar com essas linguagens e adotar o paradigma da função, sem "combater a ferramenta".
David Arno
10

Se baré dependente baz, o que por sua vez exige dependency, barrequer dependencytambém para ser usado corretamente baz. Portanto, as abordagens corretas seriam passar a dependência como um parâmetro para barou curry baze passar isso para bar.

A primeira abordagem é mais simples de implementar e ler, mas cria um acoplamento entre bare baz. 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, se bazou dependencytiver 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.

David Arno
fonte
1
Eu quase concordo completamente. A injeção de dependência pode ser outra abordagem não "hacky".
Jonathan van de Veen
1
@JonathanvandeVeen, certamente o próprio ato de passar dependencypor parâmetros é injeção de dependência?
David Arno
2
As estruturas de injeção de dependência do @DavidArno não se livram desses tipos de dependências, apenas as movem. A mágica é que eles os movem para fora de suas aulas, para um local onde testar é o Problema de Outra Pessoa.
Kevin Krumwiede
@JonathanvandeVeen Concordo, injeção de dependência é uma solução válida. Na verdade, é o que eu mais escolho.
Jamie McCrindle
1

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.

radarbob
fonte
Essa abordagem parece estranhamente familiar ...
Roger sobre a questão do acoplamento temporal (seu link), @Snowman. É importante que a ordem de execução necessária seja encapsulada.
Radarbob #