Sponsored Link •
|
Summary
Once the boilerplate traditionally associated with the Service Locator Pattern is eliminated, one of the key motivations for Dependency Injection is reduced, if not eliminated. This article, the second of a two part series, looks at the pros and cons of each approach to interacting with services.
Advertisement
|
In Part 1, we looked at Bordello, a simple implementation of the Service Locator Pattern to aid in making code more testable. Of course, the Service Locator Pattern isn't the only way to accomplish this; using a Dependency Injection framework such as Spring or Guice is another popular technique. One reason for the popularity of Dependency Injection is the perceived difficulty associated with the Service Locator Pattern. Indeed, Crazy Bob Lee touts this as one of the motivations for Guice:
Before Guice, I had to make the same up-front decision over and over: do I call a constructor directly, or do I write a factory and call it instead? If you start out calling a constructor and later decide that you need a factory, you have to go back and change all the callers. On the other hand, factories come at a cost: they clutter your API and result in a bunch of boilerplate code which you have to both write and maintain.
As we saw in Part 1, it's actually quite easy to have a lightweight
Service Locator which doesn't require writing new factories for each
service. To quickly review, client code simply
calls Bordello.get
to acquire services as it needs them.
public class HelloClient {
public void greetFormally(String surname) {
Bordello.get(HelloService.class).sayHello("Sir " + surname);
}
}
By contrast, a client using Guice will have services injected at
construction time, and might look like:
public class HelloClient {
@Inject
private HelloService service;
public void greetFormally(String surname) {
helloService.sayHello("Sir " + surname);
}
}
On the surface, DI frameworks and this sort of "generic" service
locator seem very different. It turns out, however, that while the
two approaches feel quite different, their behavior in most ways is
strikingly similar. Both use interfaces to enable testability. Both
require some means of specifying the preferred implementation of a
service; Bordello uses annotations for this (as does Guice, and later
versions of Spring), but XML or other code-based configuration could
just as easily be used, as could a convention-over-configuration
approach. To determine what DI services a class uses, you look for
member variables which are flagged for injection; to determine the
Bordello services used, look for occurrences
of Bordello.get
.
There, however are a few notable differences. Perhaps the most obvious is the ways in which each approach ties code to the framework. On the plus side for DI, a typical DI client might not have any reference to the framework which will be used to inject its dependencies. As such, client code has no dependency on the DI framework. In contrast, a client relying on Bordello will have a reference to the Bordello class. In practice, I'm not convinced that this is that great of a cost; I've not heard any cases of a project switching DI frameworks in midstream. However, while a class can reference a client which uses Bordello and be none the wiser, typically to use a client which relies on DI, one must have the DI framework provide that client. While this may not create dependencies in a formal sense, it does place certain restrictions on how one can go about acquiring instances.
Another impact of using DI is the requirement that a member
variable be declared for each injected service; depending on one's
style preferences, a setter or constructor argument might be required
as well. While this is a fairly lightweight requirement as
boilerplate goes, it does have a couple undesirable side effects. The
first effect is that (in the case of using setter methods or
constructor arguments) the services a class chooses to interact with
now become part of its API. The fact that a class is a client of
HelloService
is an implementation detail, and should not
need to be advertised. The second effect is that all of a class's
dependencies must be instantiated when an instance of that class is
first created. Because of the transitive nature of DI, this
effectively means creating a whole object graph upon startup. On the
up side, this means that a DI framework can catch certain problems
with circular or undefined dependencies early rather than late. On
the down side, if you want to lazily construct a service on an
as-needed basis, you are forced to go back to the Factory Pattern, and
just inject a factory for your service, rather than the service
itself. Notably, if that service has its own requirements, the
factory may need to explicitely reference the DI framework in order
to inject dependencies.
A nice feature of Bordello is that it is self-managing; no start up code is required. While DI frameworks generally require some initial bootstrap work to create a container, Bordello is designed to create service instances upon first request, with no prior initialization work. This is particularly handy for libraries which want to avoid creating any unnecessary usage requirements for their clients.
One thing that Bordello does not attempt to do is provide multiple
different implementations of a service in production. This could
certainly be done; for example, an @Implementors
annotation could contain multiple @Implementation
annotations, each distinguished by some sort of key. At my company,
however, we have simply found very little need for this. Notably, we
do not produce shrink-wrapped software which would need to be deployed
in a variety of environments, so we don't need to, for example,
support multiple different ORM strategies.
An area easily addressed by both approaches is configuration of
services. For example, one can have Spring configure a service by
providing configuration information in their beans.xml file, which
Spring will then inject into their instances. With Bordello,
configuration is up to the instance itself; it helps therefore to have
a ConfigurationService
which services can use; this
service of course can be acquired through Bordello.
Despite the cosmetic differences between the Service Locator Pattern and Dependency Injection, the two are functionally quite similar. The biggest difference between them is not what can be accomplished, or even accomplished easily, but rather the style of programming that each requires. The Service Locator Pattern is a code-focused approach; when an instance of a service is needed, code explicitely calls out to a service locator to obtain it. In contrast, Dependency Injection is a more extralinguistic approach, providing clients their dependencies without any explicit action on the clients behalf.
Have an opinion? Readers have already posted 12 comments about this weblog entry. Why not add yours?
If you'd like to be notified whenever Ian Robertson adds a new entry to his weblog, subscribe to his RSS feed.
Ian Robertson is an application architect at Verisk Health. He is interested in finding concise means of expression in Java without sacrificing type safety. He contributes to various open source projects, including jamon and pojomatic. |
Sponsored Links
|