25

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 Answers3

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.

Scant Roger
  • 9,086
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.

David Arno
  • 39,599
  • 9
  • 94
  • 129
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.

radarbob
  • 5,833
  • 20
  • 33