Está en la página 1de 48

Inversion of Control Containers and the Dependency Injection Pattern

Keyvan Nayyeri http://nayyeri.net

Main Software Engineering Research Areas

Silver bullets Proving the obvious with statistics (i.e. The Mythical Man-Month) Abstraction

The Last Word First

Abstraction

Component vs. Service

Component is a glob of software that is built to be used. User of the component can alter/extend it by the means provided but cant change the source code. Component can be used locally (i.e. DLL, assembly, jar). Service is similar to a component and is built to be used without change but it can be used remotely through remote interfaces. We focus on service in this context but almost all the concepts can be applied to components as well.

The Problem

There is usually a dependency between implementation of different components, libraries, or classes. The most common example is the dependency between implementation of a class and underlying data storage system. Replacing a class is difficult because it requires changing all the dependent classes.

An Example
We want to retrieve a list of movies using a third class, called finder.
class MovieLister...
public Movie[] moviesDirectedBy(String arg) {

List allMovies = finder.findAll();


for (Iterator it = allMovies.iterator(); it.hasNext();) {

Movie movie = (Movie) it.next();


if (!movie.getDirector().equals(arg)) it.remove();

}
return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]); }

An Example
We abstract the implementation using an interface.
public interface MovieFinder {
List findAll(); }

An Example
We can plug in the implementation.
class MovieLister... private MovieFinder finder; public MovieLister() { finder = new ColonDelimitedMovieFinder("movies1.txt");

An Example
Here is the situation where MovieLister is dependent on MovieFinder and its implementation but we prefer to have a single dependency on the interface only.

What is Inverted?

We plug in the implementation at a later point out of the hand of the original author. In real world there are many services and components that we abstract through interfaces. When deploying such a system, we need to handle the interaction with these services so we have different implementations for different deployments.

What is Inverted?
Its puzzling to say that these classes are special because they use Inversion of Control. These containers ensure that any user of a plugin follows some convention that allows separate assemblers to inject the implementation. Obviously, we need a better naming because Inversion of Control is too generic.

Inversion of Control

We use the term Dependency Injection as a better and more specific replacement for Inversion of Control. Dependency Injection is not the only way to remove dependency from application class to the plugin implementation. The other pattern is called Service Locator.

Main Derivations of Inversion of Control

Dependency Injection Service Locator

Dependency Injection
The basic idea is to have a separate object (assembler) that populates a field in the lister class with appropriate implementation of the finder class.

Frameworks
Java

Spring PicoContainer

.NET

Ninject StructureMap Unity Spring.NET

Main Styles of Dependency Injection

Constructor Injection (Type 3) Setter Injection (Type 2) Interface Injection (Type 1)

Constructor Injection
We use PicoContainer to inject the finder implementation into the lister class.
class MovieLister... public MovieLister(MovieFinder finder) { this.finder = finder; }

Constructor Injection
We also inject the file name into the container.
class ColonMovieFinder... public ColonMovieFinder(String filename) { this.filename = filename; }

Constructor Injection
Then we need to map the implementations to interfaces for PicoContainer.
private MutablePicoContainer configureContainer() { MutablePicoContainer pico = new DefaultPicoContainer(); Parameter[] finderParams = {new ConstantParameter("movies1.txt")}; pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams); pico.registerComponentImplementation(MovieLister.class); return pico; }

Constructor Injection
Now test it in action.
public void testWithPico() { MutablePicoContainer pico = configureContainer(); MovieLister lister = (MovieLister) pico.getComponentInstance(MovieLister.class); Movie[] movies = lister.moviesDirectedBy("Sergio Leone"); assertEquals("Once Upon a Time in the West", movies[0].getTitle()); }

Setter Injection
We define a setting method for the lister class.
class MovieLister... private MovieFinder finder; public void setFinder(MovieFinder finder) { this.finder = finder; }

Setter Injection
We also define a setter for file name.
class ColonMovieFinder... public void setFilename(String filename) { this.filename = filename; }

Setter Injection
Now we set up the XML configuration file.
<beans> <bean id="MovieLister" class="spring.MovieLister"> <property name="finder"> <ref local="MovieFinder"/> </property> </bean> <bean id="MovieFinder" class="spring.ColonMovieFinder"> <property name="filename"> <value>movies1.txt</value> </property> </bean> </beans>

Setter Injection
Finally, we can test it.
public void testWithSpring() throws Exception { ApplicationContext ctx = new FileSystemXmlApplicationContext("spring.xml"); MovieLister lister = (MovieLister) ctx.getBean("MovieLister"); Movie[] movies = lister.moviesDirectedBy("Sergio Leone"); assertEquals("Once Upon a Time in the West", movies[0].getTitle()); }

Interface Injection
We define an interface to perform the injection through.
public interface InjectFinder { void injectFinder(MovieFinder finder); }

Interface Injection
Any class that wants to use a finder, must implement the interface.
class MovieLister implements InjectFinder... public void injectFinder(MovieFinder finder) { this.finder = finder; }

Interface Injection
We can also inject the file name into the finder implementation.
public interface InjectFinderFilename { void injectFilename (String filename); } class ColonMovieFinder implements MovieFinder, InjectFinderFilename...... public void injectFilename(String filename) { this.filename = filename; }

Interface Injection
Time for configuration. This configuration consists of two steps: Registering components Registering injectors
class Tester... private Container container; private void configureContainer() { container = new Container(); registerComponents(); registerInjectors(); container.start(); }

Interface Injection
Registering the components through lookup keys.
class Tester... private void registerComponents() { container.registerComponent("MovieLister", MovieLister.class); container.registerComponent("MovieFinder", ColonMovieFinder.class); }

Interface Injection
Registering the injectors by registering injector objects with the container.
class Tester... private void registerInjectors() { container.registerInjector(InjectFinder.class, container.lookup("MovieFinder")); container.registerInjector(InjectFinderFilename.class, new FinderFilenameInjector()); }
public interface Injector { public void inject(Object target); }

Interface Injection
Here it makes sense to implement the injector interface itself.
class ColonMovieFinder implements Injector...... public void inject(Object target) { ((InjectFinder) target).injectFinder(this); } class Tester... public static class FinderFilenameInjector implements Injector { public void inject(Object target) { ((InjectFinderFilename)target).injectFilename("movies1.txt"); } }

Interface Injection
Finally, we can test it.
class IfaceTester... public void testIface() { configureContainer(); MovieLister lister = (MovieLister)container.lookup("MovieLister"); Movie[] movies = lister.moviesDirectedBy("Sergio Leone"); assertEquals("Once Upon a Time in the West", movies[0].getTitle()); }

Service Locator
Use an object that returns a specific service. Service Locator in this example returns a movie finder when needed.

Service Locator
We use the ServiceLocator as a singleton Registry.
class MovieLister... MovieFinder finder = ServiceLocator.movieFinder(); class ServiceLocator... public static MovieFinder movieFinder() { return soleInstance.movieFinder; } private static ServiceLocator soleInstance; private MovieFinder movieFinder;

Service Locator
Like injection, we need to configure the Service Locator.
class Tester... private void configure() { ServiceLocator.load(new ServiceLocator(new ColonMovieFinder("movies1.txt"))); } class ServiceLocator... public static void load(ServiceLocator arg) { soleInstance = arg; } public ServiceLocator(MovieFinder movieFinder) { this.movieFinder = movieFinder; }

Service Locator
We can test the code.
class Tester... public void testSimple() { configure(); MovieLister lister = new MovieLister(); Movie[] movies = lister.moviesDirectedBy("Sergio Leone"); assertEquals("Once Upon a Time in the West", movies[0].getTitle()); }

Segregated Interface
The problem is the lister class is dependent on the full Service Locator class while it uses a part of that. We can resolve this issue using a segregated interface.
public interface MovieFinderLocator { public MovieFinder movieFinder();

Segregated Interface
The locator needs to implement the segregated interface to provide access to finder.
MovieFinderLocator locator = ServiceLocator.locator(); MovieFinder finder = locator.movieFinder(); public static ServiceLocator locator() { return soleInstance; } public MovieFinder movieFinder() { return movieFinder; } private static ServiceLocator soleInstance; private MovieFinder movieFinder;

Dynamic Service Locator


We can modify the static Service Locator to stash any service you need into it and make the choice at runtime. Here we use maps instead of fields for each service.
class ServiceLocator... private static ServiceLocator soleInstance; public static void load(ServiceLocator arg) { soleInstance = arg; } private Map services = new HashMap(); public static Object getService(String key){ return soleInstance.services.get(key); } public void loadService (String key, Object service) { services.put(key, service); }

Dynamic Service Locator


Configuration is yet a common step of the process.
class Tester... private void configure() { ServiceLocator locator = new ServiceLocator(); locator.loadService("MovieFinder", new ColonMovieFinder("movies1.txt")); ServiceLocator.load(locator); }

Dynamic Service Locator


It can be used with a very simple code.
class MovieLister... MovieFinder finder = (MovieFinder) ServiceLocator.getService("MovieFinder");

Injection and Locator Together


Dependency Injection and Service Locator are not exclusive concepts and can be used together. The following example uses them both in Avalon. It uses Service Locator but applies injection to tell components where to find the locator.
public class MyMovieLister implements MovieLister, Serviceable { private MovieFinder finder; public void service( ServiceManager manager ) throws ServiceException { finder = (MovieFinder)manager.lookup("finder"); }

Dependency Injection vs. Service Locator

With Service Locator every user has a dependency to the locator which hides dependencies to other implementations. Using Dependency Injection, you can easily discover the dependencies, but with the Service Locator you need to search the source code. Generally, Dependency Injection is more common because it works better with Test-Driven Development to simplify the testing (i.e. applying mock objects).

Constructor vs. Setter Injection

It mirrors a general issue with ObjectOriented programming: filling fields in a constructor or using setter methods. Constructor initialization has the advantage to hide any fields that are immutable. Constructor approach has a problem: distinguishing multiple parameters of simple types. Generally, its better to have both options but prefer constructor approach.

Code vs. Configuration Files

If there are many deployments, configuration approach makes more sense. If there is a conditional logic and complex scenarios, human-readable code is better. The best approach is to provide a programmatic interface and treat a separate configuration file as an optional feature.

References

Inversion of Control Containers and the Dependency Injection pattern (by Martin Fowler) Inversion of Control (by Martin Fowler)

Summary

Abstraction is one of the main goals of Software Engineering. Inversion of Control is yet another technique to accomplish abstraction. Dependency Injection and Service Locator are two variations of Inversion of Control. There are three styles of Dependency Injection. Constructor injection is the most common style.

Questions

Do you have any questions?

También podría gustarte