Modèles de passage de contexte dans une chaîne de méthodes

19

Il s'agit d'une décision de conception qui semble beaucoup revenir: comment passer du contexte à travers une méthode qui n'en a pas besoin à une méthode qui en a besoin. Y a-t-il une bonne réponse ou cela dépend-il du contexte?

Exemple de code nécessitant une solution

// 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);
}

Solutions possibles

  • threadlocal
  • global
  • objet de contexte
  • passer la dépendance
  • curry baz et passez-le dans la barre avec la dépendance définie comme premier argument
  • injection de dépendance

Exemples d'où vient

Traitement des requêtes HTTP

Les objets de contexte sous forme d'attributs de requête sont souvent utilisés: voir expressjs, Java Servlets ou owin de .net.

Enregistrement

Pour la journalisation Java, les gens utilisent souvent des globales / singletons. Voir les modèles typiques de journalisation log4j / commons logging / java.

Transactions

Les sections locales de thread sont souvent utilisées pour conserver une transaction ou une session associée à une chaîne d'appels de méthode pour éviter d'avoir à les transmettre en tant que paramètres à toutes les méthodes qui n'en ont pas besoin.

Jamie McCrindle
la source
S'il vous plaît utiliser un exemple plus significatif.
Tulains Córdova
J'ai ajouté quelques exemples de on bute.
Jamie McCrindle
3
Je voulais dire un exemple de code plus significatif.
Tulains Córdova

Réponses:

11

La juste réponse est que cela dépend des idiomes de ParadigmMD de programmation. Si vous utilisez OO, il est presque certainement incorrect de passer une dépendance d'une méthode à l'autre. C'est une odeur de code dans OO. En fait, c'est l'un des problèmes que OO résout - un objet corrige un contexte. Donc, dans OO, une approche correcte (il y a toujours d'autres façons) est de fournir la dépendance via un constructeur ou une propriété. Un commentateur mentionne "Dependency Injection" et c'est parfaitement légitime, mais ce n'est pas strictement nécessaire. Fournissez simplement la dépendance afin qu'elle soit disponible en tant que membre de fooet baz.

Vous mentionnez le curry, donc je suppose que la programmation fonctionnelle n'est pas hors de question. Dans ce cas, un équivalent philosophique du contexte d'objet est la fermeture. Toute approche qui, une fois de plus, fixe la dépendance il est donc disponible à charge fonctionne très bien. Le curry est une de ces approches (et il vous fait paraître intelligent). N'oubliez pas qu'il existe d'autres façons de fermer une dépendance. Certains d'entre eux sont élégants et certains horribles.

Ne pas oublier la programmation par aspects . Il semble être tombé en disgrâce ces dernières années, mais son objectif principal est de résoudre exactement le problème que vous décrivez. En fait, l'exemple d'aspect classique se connecte. Dans AOP, la dépendance est ajoutée automatiquement après l'écriture d'un autre code. Les gens AOP appellent cela « tissage ». Les aspects communs sont tissés dans le code aux endroits appropriés. Cela rend le code plus facile de réfléchir et est assez cool de reprise, mais il ajoute également une nouvelle charge d'essai. Vous aurez besoin d'un moyen de déterminer si vos artefacts finaux sont sains. AOP a aussi des réponses à cela, alors ne vous sentez pas intimidé.

Scant Roger
la source
Prétendre que le passage de paramètres dans les méthodes OO est une odeur de code est une déclaration très controversée. Je dirais le contraire: encourager le mélange de l'état et de la fonctionnalité au sein d'une classe est l'une des plus grandes erreurs commises par le paradigme OO et l'éviter en injectant des dépendances directement dans les méthodes, plutôt que via un constructeur est le signe d'une pièce bien conçue de code, OO ou non.
David Arno
3
@DavidArno je suggère d' utiliser un objet de finale contre de paradigme différent statefulness est « l' une des plus grandes erreurs marque par le paradigme OO », puis contourner le paradigme. Je n'ai rien contre presque n'importe quelle approche, mais je n'aime généralement pas le code où l'auteur se bat contre son outil. Privé de l' Etat est un trait distinctif de OO. Si vous évitez cette fonctionnalité, vous perdez une partie de la puissance d'OO.
Scant Roger
1
@DavidArno Une classe qui est tout état et aucune fonctionnalité n'a aucun mécanisme pour appliquer des relations invariantes dans l'état. Une telle classe est pas du tout OO.
Kevin Krumwiede
@KevinKrumwiede, dans une certaine mesure, vous avez appliqué reducto ad absudium à mon commentaire, mais votre argument est toujours bien fait. L'invariance sur l'état est un élément important de la "sortie d'OO". Ainsi, éviter de mélanger la fonctionnalité et l'état doit permettre suffisamment de fonctionnalités dans un objet d'état pour atteindre l'invariance (champs encapsulés définis par le constructeur et accessibles via des getters).
David Arno
@ScantRoger, je suis d' accord un autre paradigme peut être adoptée, à savoir le paradigme fonctionnel. Fait intéressant, la plupart des langages "OO" modernes ont une liste croissante de fonctionnalités et il est donc possible de s'en tenir à ces langages et d'adopter le paradigme des fonctions, sans "combattre l'outil".
David Arno
10

Si bardépend baz, ce qui nécessite à son tour dependency, alors barnécessite dependencyaussi afin d'utiliser correctement baz. Par conséquent, les approches correctes consisteraient soit à transmettre la dépendance en tant que paramètre à bar, soit à curry bazet à la transmettre à bar.

La première méthode est plus simple à mettre en œuvre et de lire, mais crée un couplage entre baret baz. La deuxième approche supprime ce couplage, mais pourrait entraîner un code moins clair. Ce qui est la meilleure approche dépend donc probable de la complexité et le comportement des deux fonctions. Par exemple, si bazou dependencyavoir des effets secondaires, facilité d'essais sera sans doute un grand pilote dans lequel la solution choisie.

Je suggérerais que toutes les autres options que vous proposez sont à la fois "hacky" par nature et susceptibles d'entraîner des problèmes à la fois avec les tests et avec des bogues difficiles à détecter.

David Arno
la source
1
Je suis presque entièrement d'accord. L' injection de dépendance peut être une autre non - aproche « aki ».
Jonathan van de Veen
1
@JonathanvandeVeen, sûrement l'acte même de passer dependencyvia les paramètres est l'injection de dépendance?
David Arno
2
@DavidArno Les frameworks d'injection de dépendances ne se débarrassent pas de ces types de dépendances, ils les déplacent simplement. La magie est qu'ils les déplacent en dehors de vos cours, à un endroit où le test est le problème de quelqu'un d'autre.
Kevin Krumwiede
@JonathanvandeVeen Je suis d'accord, l'injection de dépendances est une solution valable. En fait , il est celui que je le plus souvent choisir.
Jamie McCrindle
1

Parler philosophiquement

Je suis d' accord avec la préoccupation de David Arno .

Je lis l'OP comme la recherche de solutions de mise en œuvre. Cependant, la réponse est changer la conception . "Motifs"? Conception OO est, pourrait - on dire, question de contexte. Il est une grande, une feuille de papier vierge enceinte de possibilités.

Le traitement du code existant est un contexte bien différent.



Je travaille sur « zactly le même problème en ce moment. Eh bien, je corrige les centaines de lignes de copier-coller de code qui ont été effectuées juste pour qu'une valeur puisse être injectée.

Modularisation du code

J'ai jeté 600 lignes de code en double puis refactorisé donc au lieu de "A appelle B appelle C appelle D ..." J'ai "Appel A, retour, Appel B, retour, Appel C ...". Il ne nous reste plus qu'à injecter la valeur dans l'une de ces méthodes, disons la méthode E.

Ajout d'un paramètre par défaut au constructeur. Les appelants existants ne changent pas - "facultatif" est le mot clé ici. Si aucun argument n'est transmis, la valeur par défaut est utilisée. Ensuite, une seule ligne change pour passer la variable dans la structure modulaire refactorisée; et un petit changement dans la méthode E pour l'utiliser.


Fermetures

Un thread de programmeurs - "Pourquoi un programme utiliserait-il une fermeture?"

Pour l'essentiel, vous injectez des valeurs dans une méthode qui retourne une méthode sur mesure avec les valeurs. Cette méthode mesure est ensuite exécuté.

Cette technique vous permettrait de modifier une méthode existante sans changer sa signature.

radarbob
la source
Cette approche ressemble étrangement familier ...
Roger sur la question du couplage temporel (votre lien), @Snowman. Il est important que l'ordre d'exécution requise est encapsulé.
radarbob