TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

Dependency injection is dynamic scoping in disguise

199 pointsby r4umover 5 years ago

23 comments

deniswover 5 years ago
I really enjoyed the comparison of dependency injection with dynamic scoping, and the explanation of how the latter can take over the uses cases of the former with less boilerplate.<p>But one benefit of dependency injection unacknowledged in this article is that dependency injection is the explicitness of dependencies: the need to pass them in forces the caller to be aware of which dependencies exist, and changes in dependencies cannot be ignored (they lead to compilation errors in statically typed languages, at least).<p>Managing dependencies with dynamic variables, on the other hand, is implicit. It&#x27;s impossible to know which parts of the dynamic environment are used by a module without inspecting its source code. And changes to the module&#x27;s dependencies are not noticed by callers, which may lead to cases where tests fail to stub out particular side effects without anyone noticing.<p>Given this drawback, dependency injection still seems like the better trade-off to me, despite of its higher amount of required boilerplate. Perhaps it is possible to bring some of the explicitness to the dynamic scoping approach, though.
评论 #21408986 未加载
评论 #21410783 未加载
评论 #21408540 未加载
评论 #21411684 未加载
评论 #21412255 未加载
评论 #21409659 未加载
评论 #21411237 未加载
评论 #21413594 未加载
crypticaover 5 years ago
I agree that dependency injection and dynamic scoping are similar and I think they are both anti-patterns.<p>It doesn&#x27;t make sense to stub out dependencies in unit tests (unless you absolutely have to). Stubbing out dependencies is like stubbing out native functions, operators or loops. They are called dependencies for a good reason; because your class depends on them and assumes that they work. Trying to make dependencies substitutable is overengineering and leads to poor design and gives you more work when implementing unit tests.<p>Dependency injection is particularly bad because it makes it difficult to find the path to the dependency&#x27;s source code (which is critical for debugging). Hiding the source path of a dependency is a terrible anti-pattern. When it comes to programming, there are few things more horrible than not being able to determine where some buggy piece of logic is located. I cannot imagine any use case where that would be a fair tradeoff.<p>Any kind of injection of dependencies should be done via an explicit method&#x2F;constructor parameter. Sometimes it means that the dependency instance has to traverse a few classes in the hierarchy, but that&#x27;s way better because at least you can unambiguously track where the dependency came from. Also, if the dependency has to traverse A LOT of classes, then you know there is probably something wrong with your architecture (e.g. a dependency imported too high in the hierarchy and used in a deeply nested class can indicate poor separation of concerns and leaky abstraction between components).
评论 #21407428 未加载
评论 #21408376 未加载
评论 #21407162 未加载
评论 #21407110 未加载
评论 #21408391 未加载
评论 #21407337 未加载
评论 #21408182 未加载
评论 #21407423 未加载
评论 #21412579 未加载
评论 #21408500 未加载
评论 #21411560 未加载
评论 #21407417 未加载
评论 #21410256 未加载
评论 #21406898 未加载
评论 #21408308 未加载
glunover 5 years ago
Author here. After posting this to reddit I realized that the original title is wrong, and poorly reflects the actual point I&#x27;m trying to make. Dependency injection is not dynamic scoping, but the latter can be used to achieve the former. I&#x27;m drafting an update to better reflect this. I&#x27;m also going to pull out reader monads and env passing into separate sections and give reader monads a better treatment in general.
评论 #21407703 未加载
评论 #21407454 未加载
评论 #21417274 未加载
评论 #21429238 未加载
评论 #21417664 未加载
评论 #21417392 未加载
评论 #21413988 未加载
评论 #21414636 未加载
评论 #21407097 未加载
matheusmoreiraover 5 years ago
These patterns are almost always working around language shortcomings.<p>For example, factories work around the new keyword. The new keyword in Java emits the constructed type into the bytecode, making it a hard ABI dependency. So people invented factories: they hide the new keyword behind methods that return interfaces. In better languages such as Smalltalk, new is just a method that can be overridden.<p>Singletons work around the fact only objects can implement interfaces. Classes are natural singletons and yet they are second class citizens of most languages. It is not possible to pass the class itself to code expecting some interface. So people are forced to create an object and add complicated boilerplate code to prevent more than one instance from ever being created. In better languages, classes are the same as objects, they can conform to interfaces and be passed around normally.
评论 #21409916 未加载
评论 #21412011 未加载
评论 #21409677 未加载
评论 #21412040 未加载
评论 #21409888 未加载
barrkelover 5 years ago
DI is a module system built out of classes; dynamic scoping is another way to build a module system.<p>If you had a good composable (parameterized) module system, you&#x27;d have much less need of DI. A composable module system would scope the lookup of type names and static methods to the actual module arguments arguments the accessing module is constructed with.<p>The problem with `new X()` vs `@Inject X x` is that the construction of X in the former has no indirection; type names are global constants. A module system provides an indirection. Dynamic scoping could also provide an indirection, because dynamic scoping lets you redefine &#x2F; redirect those otherwise constant things.<p>(DI in practice does a bunch more, like proxies to let you put data with different lifetimes (request, session) in a mixed object graph; and the fact that proxies now exist means aspect-oriented programming sticks its head in and encourages its use for things like auth and transactions. Once you go over the edge of the DI barrier to acceptance, &quot;best practices&quot; shift dramatically - you end up quite far from where you started.)
adrianmonkover 5 years ago
&gt; <i>Second, we can now pass in different implementations of our dependencies when executing in test. This is very good, but let me rephrase that in more general terms: the values associated with certain names are now dependent on the environment in which we are executing.</i><p>Earlier, they established the idea that this was a bad thing by having the printGreeting() function print a message you probably don&#x27;t want. The change in the value caused bad behavior.<p>However, with dependency injection, you should be following the Liskov substitution principle. You might get different values, but they should all be following the same contract.<p>The acceptOrder() function might get different implementations of BankService, but the difference should be opaque to it. Calling bank.chargeMoney() should work the same regardless of which one you got.<p>The reason I bring this up is the crux of the argument presented here against dynamic scoping is, &quot;Dynamic scoping makes it hard to figure what our program actually does, without executing it, and that’s not a quality we want our programs to exhibit.&quot;<p>To the extent that you successfully pull off having different subclasses follow the same contract, this weakness doesn&#x27;t really apply to DI (or inversion of control).
zubspaceover 5 years ago
&gt; The problem with this style of programming is of course that we have to pass the Env around everywhere<p>So why not make it a singleton? Or even better, make it a static class with some static properties?<p>Yes I know, I said something evil! But before you take out the pitchforks, bear with me:<p><pre><code> public static class Master { public static ISupplyService Supply; public static IBankService Bank; public static IMailService Mailer; } </code></pre> This is C# and I actually LOVE this pattern. I have seen it referred to as Master-Pattern, but I don&#x27;t know the correct term. It solves a lot of problems:<p>* You don&#x27;t have to pass essential modules around anymore. You can initialize the Master once and access them everywhere without caring about their implementation.<p>* Code completion does wonders on this one. You simply type &quot;Mast...&quot; and it will show you ALL available modules. You don&#x27;t have to remember anything. It&#x27;s awesome for new team members.<p>* It is fast. In release builds you can even try to replace the interfaces with the actual classes implementing it for a straight method call.<p>It introduces a few problems:<p>* You increase coupling. Once you add a module, removing it or significantly changing the interface is tricky.<p>* You need to be careful to NOT couple modules to each other, and if you do, do it rarely. Otherwise you will have to instantiate the modules in a specific order which gets cumbersome.<p>In my opinion: If you are a small team and have sane teammates, give this one a try. It reduces boilerplate a lot and reduces the amount of arguments you need to pass around everywhere.
评论 #21409040 未加载
评论 #21408901 未加载
评论 #21408762 未加载
评论 #21411783 未加载
评论 #21408834 未加载
msluyterover 5 years ago
In the java OrderService example, the author writes:<p>&quot;Second, we can now pass in different implementations of our dependencies when executing in test3. This is very good, but let me rephrase that in more general terms: the values associated with certain names are now dependent on the environment in which we are executing. This should sound very familiar, dependency injection is just a more controlled form of dynamic scoping.&quot;<p>This seems slightly off to me, unless I&#x27;m misremembering my java. In the OrderService example, the <i>value</i> of the variable `bank` cannot change because it&#x27;s a reference: it&#x27;ll always refer to the same object. However, if the instantiated `BankService` is mutable, then the internals of that object could change in various ways. Hence, in practice, the dangers of this pattern only seem problematic if dependencies have mutable state.<p>Back when I was doing java, we used spring beans everywhere for this sort of thing and iirc, they had no mutable state. In Python, I use a similar pattern a lot, where I have classes that are in practice &#x27;immutable once initialized&#x27; -- though of course, in Python you could always mess around with the internals at runtime -- which are segregated from classes or objects that have mutable state. (Similar to the structure described here: <a href="https:&#x2F;&#x2F;medium.com&#x2F;unbabel&#x2F;refactoring-a-python-codebase-using-the-single-responsibility-principle-ed1367baefd6" rel="nofollow">https:&#x2F;&#x2F;medium.com&#x2F;unbabel&#x2F;refactoring-a-python-codebase-usi...</a>)<p>Of course, I get that you can&#x27;t in practice know how stateful everything in your dependency graph is. But I think the real problem here (if there is one) isn&#x27;t explicit DI in the form of dependency passing, but (unexpected&#x2F;hidden) object mutability.
jakub_gover 5 years ago
[Offtopic info for the author]<p>I see [1] you&#x27;re using &lt;frameset&gt; to wrap GitHub Pages with your own domain. You could do it in a less hacky way by creating a CNAME file in GitHub repo, and updating GitHub repo settings + DNS settings of the domain:<p><a href="https:&#x2F;&#x2F;help.github.com&#x2F;en&#x2F;github&#x2F;working-with-github-pages&#x2F;managing-a-custom-domain-for-your-github-pages-site" rel="nofollow">https:&#x2F;&#x2F;help.github.com&#x2F;en&#x2F;github&#x2F;working-with-github-pages&#x2F;...</a><p>(Unless there&#x27;s some particular way why you don&#x27;t want it? I&#x27;d be curious. Ability to gather server-side logs?)<p>[1] learnt it because the page fails to load with uMatrix extension so I checked source.
评论 #21411880 未加载
cousin_itover 5 years ago
With dynamic scoping, when you need a DatabaseConnection, someone up the stack from you still has to construct it manually. With dependency injection, the framework can construct it for you.<p>I think maybe a better analogy for dependency injection is imports. When library A imports library B which imports library C, they can just declare that, nobody needs to assemble &quot;new A(new B(new C()))&quot;. Dependency injection is the same thing, but instead of libraries you have stateful objects, like &quot;a RequestHandler needs a RequestContext which needs a DatabaseConnection&quot;. Maybe these tasks could even be handled by the same tool, but I haven&#x27;t seen such a tool yet.
评论 #21408127 未加载
thunderbongover 5 years ago
This is my favourite explanation on dependency injection -<p><a href="https:&#x2F;&#x2F;www.jamesshore.com&#x2F;Blog&#x2F;Dependency-Injection-Demystified.html" rel="nofollow">https:&#x2F;&#x2F;www.jamesshore.com&#x2F;Blog&#x2F;Dependency-Injection-Demysti...</a><p>From the article -<p><i>The Really Short Version</i><p><i>Dependency injection means giving an object its instance variables. Really. That&#x27;s it.</i>
drblastover 5 years ago
I think dependency injection can have its uses, but the way I see it used in practice it looks like someone should file a bug on whatever programming language features are missing to make dependency injection necessary.<p>I think <i>most</i> code should be purely functional and unit tested that way, which means that the only dependencies are the input parameters. &quot;Mock&quot; dependencies used in unit tests are usually a unit-test circle jerk; most of the time you&#x27;re essentially testing that your programming language can indeed make method calls through an interface and those tests are only there because you added DI in the first place. It&#x27;s common to see all kinds of testing like this but nothing testing the actual functionality of the code because that&#x27;s so obscured by DI or mocked out. It <i>feels</i> like you&#x27;re implementing comprehensive testing but it&#x27;s mostly just additional complexity obscuring the fact that you&#x27;re not actually testing anything real.<p>The code that can&#x27;t be functional? Sure, go ahead and knock yourself out with dependency injection and IOC. It&#x27;s great for not having to pass configuration and logging instances around. But it&#x27;s being abused when it&#x27;s all over the place and you can&#x27;t look at code and figure out what it does without also looking at configuration files and startup classes and knowing how the flavor-of-the-month DI framework works.
larzangover 5 years ago
I don&#x27;t really see this comparison as being particularly useful. There isn&#x27;t really a choice between DI or dynamic scoping, there&#x27;s just a choice between application architectures, which is largely dictated by language.<p>The Closure example works because all your foreign symbols are coming from namespaced imports, which is to say you have a Module architecture and your only polymorphism lever is altering the namespace mapping to point to an equivalent module.<p>With Java and the like you have a Service architecture, so rather than importing symbols you&#x27;re injecting services. Things that would otherwise be exported as bare functions tend to be written as classes so that they can be used as services.<p>While some languages can support either (i.e. JS is typically modular but there are things like Bottle if you want to write JS in a service-based way), it&#x27;s not typical that a given application is going to support either simultaneously. Most languages have a clear idiomatic choice that you&#x27;re likely not going to want to stray from to avoid headaches integrating 3rd party dependencies and developers other than yourself.
CGamesPlayover 5 years ago
In JavaScript, Jest provides this using module mocks. The code under test imports a module, but in the test environment that module has been swapped out with a mocked implementation. <a href="https:&#x2F;&#x2F;jestjs.io&#x2F;docs&#x2F;en&#x2F;jest-object#jestmockmodulename-factory-options" rel="nofollow">https:&#x2F;&#x2F;jestjs.io&#x2F;docs&#x2F;en&#x2F;jest-object#jestmockmodulename-fac...</a>
moutansosover 5 years ago
One language that actually utilizes true dynamic scoping is PowerShell. It&#x27;s true that this is an extremely powerful idea that can even override imported functions from a parent scope for things like testing, but it can very much be a nightmare. It leaves the programmer completely unaware of where a function or variable is declared or if it even is declared. Imagine the situation that while testing you have overridden a piece of code that is an interface into a real data layer in a real system, and you forget to declare it in the parent scope of the test, or worse yet, declare it and misspell the name of the function you should be overriding. You accidentally call the real function and start manipulation of data in a real system. It becomes a nightmare. DI and IoC don&#x27;t have these issues because they rely on explicit passing of the dependency. So like many things, with great power comes great responsibility.
caternover 5 years ago
&gt;This should sound very familiar, dependency injection is just a more controlled form of dynamic scoping.<p>This is not really true. First off, this is too much focus on &quot;dependency injection&quot;, which is just one usage of a more general feature: passing arguments to functions.<p>Is dynamic scoping the same thing as passing arguments to functions? Well, they are certainly closely related; for example, see the &quot;implicit parameters&quot; paper[0]. But I think it is incorrect to say that converting a function to take more arguments is &quot;emulating dynamic scoping&quot;, any more than function arguments in general are &quot;emulating dynamic scoping&quot;.<p>[0] <a href="https:&#x2F;&#x2F;www.researchgate.net&#x2F;publication&#x2F;2808232_Implicit_Parameters_Dynamic_Scoping_with_Static_Types" rel="nofollow">https:&#x2F;&#x2F;www.researchgate.net&#x2F;publication&#x2F;2808232_Implicit_Pa...</a>
FpUserover 5 years ago
Satisfying dependency can be setting up some pointer to a function, passing an instance of already created interface, asking some factory to supply it etc. etc. As in everything in life there is no single universal answer on what is the best way to accomplish the task. What one uses in practice depends very much from problem complexity, one&#x27;s experience &#x2F; personal preferences, implementation language &#x2F; environment features etc. etc.<p>I think there is no real need to dwell over the subject without knowing particular context
ceronmanover 5 years ago
In Python&#x27;s standard library there is the unittest.mock module [1] which allows you to patch functions and methods. For example:<p><pre><code> with patch(&#x27;requests.get&#x27;, fake.get): definition = find_definition(&#x27;testword&#x27;) </code></pre> [1] <a href="https:&#x2F;&#x2F;docs.python.org&#x2F;3&#x2F;library&#x2F;unittest.mock.html" rel="nofollow">https:&#x2F;&#x2F;docs.python.org&#x2F;3&#x2F;library&#x2F;unittest.mock.html</a>
anaphorover 5 years ago
In Racket (and probably other Schemes) you can do<p>(define foo (make-parameter &quot;some value you want by default&quot;))<p>(define (do-foo) (do-something (foo))<p>(do-foo) ; uses the regular foo<p>(parameterize ([foo &quot;my injected foo]) (do-foo))<p>And it all just works as you would expect. `foo` gets automatically reset to the prior value when it leaves the scope of the current parameterize block. You can also nest them safely, or use them in macros, and IIRC even inside threads.
rb808over 5 years ago
I prefer Service Locator to DI, Martin Fowler talked about it a long time ago - not sure if he&#x27;s changed. <a href="https:&#x2F;&#x2F;martinfowler.com&#x2F;articles&#x2F;injection.html" rel="nofollow">https:&#x2F;&#x2F;martinfowler.com&#x2F;articles&#x2F;injection.html</a>
molluskover 5 years ago
I don’t quite see the benefits of Clojure’s dynamic binding over with-redefs for tests as demonstrated here. Personally I have only used Clojure’s dynamic binding very rarely, mostly in production scenarios to override some default value from a library and not for test cases.
caternover 5 years ago
&gt;Some examples of languages that use dynamic scoping by default are APL, Bash, Latex and Emacs Lisp.<p>bash isn&#x27;t dynamically scoped by default; it has only global scope by default. You have to use &quot;local&quot; to dynamically scope a variable binding.
评论 #21410732 未加载
klysmover 5 years ago
Scala implicits handle this really nicely