DI versus Service Locator
When talking about Dependency Injection, the service locator is
also mentioned, as a kind of evil twin. What is it actually?
I defined Dependency Injection in the first article as obvious
dependency passing, i.e. each class claiming its dependencies in a constructor
or other method, instead of obviously getting them from a global access
point somewhere in the body. Following this principle leads to more readable and
predictable code.
However, there is a pitfall lurking in the form of the service locator.
The service locator is a large object that can return any dependencies that
the class needs, or even doesn't need. If all classes passed one service
locator, they would pass all dependencies to each other in a single
parameter.
class Authenticator
{
private $locator;
function __construct(ServiceLocator $locator)
{
$this->locator = $locator;
}
function authenticate($user, $pass)
{
$database = $this->locator->getDatabase();
$database->query(...)
}
}
Unfortunately the service locator is not DI compliant.
Why? It is not consistent with the fact that the passing of dependencies is
obvious and that the class openly declares its dependencies.
Class Authenticator
- needs a database, but it claims a very generic service locator, which is
completely out of proportion to what it actually needs
- that it needs a database is not discoverable except by examining its
implementation
So a class must declare all of its dependencies, and only those. Otherwise,
it lies about its dependencies.
(There may be situations where requiring a service locator is correct: if the
class actually needs it as such. Perhaps for the sake of its
visualization, etc.)
What the service locator is
not
Occasionally someone will name a valid construct with the term service
locator and condemn it on that basis. This kind of reasoning is misleading. It
implies that we are using or rejecting something and I don't know why
actually.
It is important to realize what is wrong with the service locator, i.e. why
we classify it as an antipattern, and to check if our constructs suffer from the
same defects. Thus the discussion of whether it is a service locator or not is
then secondary.
As an example, let's take a refactoring of the Foo class with three
dependencies:
class Foo
{
function __construct(A $a, B $b, C $c)
}
$foo = new Foo($a, $b, $c);
We output the dependencies into a single (immutable) class:
class FooDependencies
{
function __construct(A $a, B $b, C $c)
}
class Foo
{
function __construct(FooDependencies $d)
}
$foo = new Foo(new FooDependencies($a, $b, $c));
From the DI point of view, both alternatives are correct, the classes claim
all their dependencies and only their dependencies. The objections against the
service locator do not apply here. Of course, the question is whether said
refactoring was justified and correct, but that's another story.
Similarly, in the introductory article What is Dependency Injection, where
I reduced the dependency group of class Article
in presenter to a
factory ArticleFactory
, the concern that the factory is a service
locator is not misplaced. Indeed, it does not exhibit its negative features.
Similarly, the example in the article Dependency Injection versus Lazy loading,
where Authenticator
was replaced with
AuthenticatorAccessor
, because presenter wanted to obtain the
service through lazy loading.
As can be seen, DI is not just about any kind of dependency passing. It must
be obvious. And if you are not sure if you are using the correct object design
in accordance with DI, do a “blind API” test. Hide your method bodies or
generate API documentation and the dependencies of each class must still be
clearly visible.
Sign in to submit a comment