24

This is a design decision that appears to come up quite a lot: how to pass context through a method that doesn't need it to a method that does. Is there a right answer or does it depend on the context.

Sample code that requires a 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);
}

Possible solutions

  • threadlocal
  • global
  • context object
  • pass the dependency through
  • curry baz and pass it into bar with the dependency set as the first arg
  • dependency injection

Examples of where is comes up

HTTP request processing

Context objects in the form of request attributes are often used: see expressjs, Java Servlets or .net's owin.

Logging

For Java logging folk often use globals / singletons. See the typical log4j / commons logging / java logging patterns.

Transactions

Thread locals are often used to keep a transaction or session associated with a chain of method calls to avoid needing to pass them as parameters to all the methods that don't need them.

3
  • Please use a more meaningful example. Nov 27, 2015 at 13:18
  • I've added some examples of where it comes up. Nov 27, 2015 at 13:23
  • 3
    I meant a more meaningful example code. Nov 27, 2015 at 15:46

3 Answers 3

11

The only fair answer is that it depends on your programming paradigm's idioms. If you're using OO, it's almost certainly incorrect to pass a dependency from method to method to method. It's a code smell in OO. In fact, that's one of the problems OO solves -- an object fixes a context. So in OO, one correct (there are always other ways) approach is to deliver the dependency via contructor or property. A commenter mentions "Dependency Injection" and that's perfectly legit, but it's not strictly necessary. Just provide the dependency so it's available as a member to foo and baz.

You mention currying, so I'll assume functional programming isn't out of the question. In that case, a philosophical equivalent of object context is closure. Any approach that, once again, fixes the dependency so it's available to dependents works just fine. Currying is one such approach (and it makes you sound smart). Just remember there are other ways to close over a dependency. Some of them are elegant and some of them awful.

Don't forget about Aspect-oriented programming. It seems to have fallen out of favor in the last few years, but its primary goal is solving exactly the problem you describe. In fact, the classic Aspect example is logging. In AOP, the dependency is added automatically after other code is written. AOP folks call this "weaving". Common aspects are woven into the code at the appropriate places. This makes your code easier to think about and is pretty darn cool, but it also adds a new testing burden. You'll need a way to determine your final artifacts are sound. AOP has answers for that as well, so don't feel intimidated.

9
  • Claiming that passing parameters around within OO methods is a code smell is a highly controversial statement. I'd argue the complete opposite: encouraging mixing state and functionality within a class is one of the biggest mistakes made by the OO paradigm and avoiding it by injecting dependencies directly into methods, rather than via a constructor is a sign of a well designed piece of code, whether OO or not.
    – David Arno
    Nov 27, 2015 at 17:21
  • 3
    @DavidArno I'd suggest using a different paradigm vs. concluding object statefulness is "one of the biggest mistakes make by the OO paradigm" and then circumventing the paradigm. I have nothing against almost any approach but generally dislike code where the author is fighting their tool. Private state is a distinguishing feature of OO. If you eschew that feature, you lose some of OO's power. Nov 27, 2015 at 22:21
  • 1
    @DavidArno A class that is all state and no functionality has no mechanism for enforcing invariant relationships in the state. Such a class is not at all OO. Nov 28, 2015 at 4:29
  • 1
    ... Claiming that functional patterns are preferred to OO patterns is highly controversial ;) When I say "fighting the tool", I mean losing the richness or what's possible because of an over-zealous viewpoint. I don't know your background, but if Haskell/F#/whatever got you excited about pure functional programming, maybe try SmallTalk to get you excited about pure OO programming. To be clear, I'm not claiming that OO is better than functional. Not at all. I'm just highlighting that each paradigm tackles the OP's problem in different, yet effective, ways. Nov 30, 2015 at 14:03
  • 1
    If we are talking alternative paradigms, isn't this one area monads can be used? The compuational context can be precisely captured by eg a Reader or State monad, so we can reason about the dependency as well.
    – CLF
    Dec 3, 2015 at 21:09
10

If bar is dependent on baz, which in turn requires dependency, then bar requires dependency too in order to correctly use baz. Therefore, the correct approaches would be to either pass the dependency through as a parameter to bar, or curry baz and pass that to bar.

The first approach is simpler to implement and read, but creates a coupling between bar and baz. The second approach removes that coupling, but could result in less clear code. Which approach is best will therefore likely depend on the complexity and behaviour of both functions. For example, if baz or dependency have side effects, ease-of-testing will likely be a large driver in which solution is chosen.

I'd suggest all the other options you propose are both "hacky" in nature and likely to lead to problems both with testing and with difficult to track down bugs.

4
  • 1
    I almost agree completely. Dependency Injection may be another Non-"hacky"aproach. Nov 27, 2015 at 14:35
  • 1
    @JonathanvandeVeen, surely the very act of passing dependency around via parameters is dependency injection?
    – David Arno
    Nov 27, 2015 at 16:47
  • 2
    @DavidArno Dependency injection frameworks don't get rid of these kinds of dependencies, they just move them. The magic is that they move them outside your classes, to a place where testing is Somebody Else's Problem. Nov 28, 2015 at 4:21
  • @JonathanvandeVeen I agree, dependency injection is a valid solution. In fact it's the one I'd most often pick. Nov 28, 2015 at 16:08
1

Philosophically Speaking

I agree with David Arno's concern.

I'm reading the OP as looking for implementation solutions. However, the answer is change the design. "Patterns"? OO design is, one could say, all about context. It's a vast, blank sheet of paper pregnant with possibilities.

Dealing with existing code is a different, well, context.



I'm working on 'zactly the same problem right now. Well, I'm fixing the hundreds of lines of code copy-n-paste that was done just so a value can be injected.

Modularize the code

I threw away 600 lines of duplicate code then refactored so instead of "A calls B calls C calls D ..." I have "Call A, return, Call B, return, Call C ...". Now we only need to inject the value into one of those methods, let's say method E.

Add a default parameter to the constructor. Existing callers do not change - "optional" is the operative word here. If an argument is not passed the default value is used. Then only 1 line change to pass the variable into the refactored, modular structure; and a small change in method E to use it.


Closures

A Programmers thread - "Why would a program use a closure?"

Essentially, you are injecting values into a method which returns a method customized with the values. That customized method is subsequently executed.

This technique would allow you to modify an existing method without changing its signature.

2
  • This approach looks oddly familiar...
    – user22815
    Dec 1, 2015 at 0:17
  • Roger on the temporal coupling (your link) issue, @Snowman. It is important that the required execution order is encapsulated.
    – radarbob
    Dec 1, 2015 at 4:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.