There is an huge difference between using "globals" and DI. First the path is usually not direced because you probably steps through singleton and service locator. Both are considered somehow an anti-pattern today. The reason is that we are supposed to design code testable, that give us great advantages when we need to change the code base for maintainance or for satisfying new requirements. Testability is easy to achieve if code is decoupled. As you guess global behaviors does not help in decoupling, so for example if you have code acceding a static singleton, to test such a code you need the singleton itself, you can't mock it, and this is bad since you can't stress your system as you please. Service locator seems at a first glance better: you could mock the service locator evantually if you need to test, but you have to:
- Know in advance which service(s) the system under test will ask to the locator
- Always create a "recursive mock" because you would probably mock the returned service as well.
DI on the constructor is a good way in code decoupling because you state very clearly what the object needs to run, and you can decide in a glance what to mock, stub and so on. Pay attention that DI will works and helps you just if you ensure not having the DI kernel walking as a dependency across your code: this would transorm the DI in an antipattern ( you decouple the code, but you bind it to a container ), so remember to study and really implement the composition root pattern, this will really hlep you to write better and testable code.