Let's use PHP
here for examples and illustraion purposes but the question is language and framework agnostic.
Many experts say Service Locator (anti)pattern should be avoided and recommend using Dependency Injection instead. Then we can find 3 ways of injection:
- constructor injection
- setter injection
- field (property) injection
Those injection ways work fine when dependency is relatively static and serves the whole object lifecycle. But sometimes we need completely different thing - passing contextual dependencies that are required and relevant only for specific method calls.
For example:
<?php
interface IController {
public function process($request);
}
class MyController implements IController {
public function __construct(private $dep1, private $dep2, private $dep3) {
...
}
public function process($request, $dep4, $dep5) {
...
}
}
While MyController
instance along with its dependencies $dep1
, $dep2
, $dep3
exists for a long time and can process
multiple requests, each process
call requires also short living dependencies $dep4
, $dep5
.
The problem here is those dependencies are in fact an implementation detail and given IController
interface may (should) just disallow process
method to have something but $request
argument.
With constructor injection that's quite fine because the constructor is not a part of any interface but just an implementation detail. This doesn't work for methods.
Even if we circumvent that we still are ought to pass those dependencies by hand, then we have no decoupling, no autowiring, etc.
With service locator though it's relatively easy since we can control context scopes transparently.
Is it fundamental Dependency Injection limitation and should we still use locators (either injected in constructor or passed to methods in form of context/"toolbox" objects)?
public function process($request, $dep4, $dep5)
- there's nothing wrong with this, this is no different than passing a callback (which is sometimes a closure, which is like passing an object - see, we've come full circle). You said "we still are ought to pass those dependencies by hand" - this is not neccesarily a problem. "then we have no decoupling" - what makes you say that? "no autowiring" - autowiring does not magically equal decoupling. It's the interface that theprocess
method expects $dep4 and $dep5 to have that provides decoupling. That interface is not an implementation detail.