DI versus Service Locator

10 years ago by David Grudl     edit

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.


All parts:

Further reading