Inversion of Control Containers and the Dependency Injection pattern

来源:互联网 发布:人民网软件下载 编辑:程序博客网 时间:2024/04/19 23:44

In the Java community there's been a rush of lightweightcontainers that help to assemble components from different projectsinto a cohesive application. Underlying these containers is a commonpattern to how they perform the wiring, a concept they refer under thevery generic name of "Inversion of Control". In this article I diginto how this pattern works, under the more specific name of"Dependency Injection", and contrast it with the Service Locatoralternative. The choice between them is less important than theprinciple of separating configuration from use.

23 January 2004

Photo of Martin Fowler

Martin Fowler

Translations: Chinese ·Portuguese · French · Italian · Romanian ·Spanish · Polish · Vietnamese
Find similar articles to this by looking at these tags:popular ·design ·object collaboration design · application architecture

Contents

  • Components and Services
  • A Naive Example
  • Inversion of Control
  • Forms of Dependency Injection
    • Constructor Injection with PicoContainer
    • Setter Injection with Spring
    • Interface Injection
  • Using a Service Locator
    • Using a Segregated Interface for the Locator
    • A Dynamic Service Locator
    • Using both a locator and injection with Avalon
  • Deciding which option to use
    • Service Locator vs Dependency Injection
    • Constructor versus Setter Injection
    • Code or configuration files
    • Separating Configuration from Use
  • Some further issues
  • Concluding Thoughts

One of the entertaining things about the enterprise Java world isthe huge amount of activity in building alternatives to the mainstreamJ2EE technologies, much of it happening in open source. A lot of thisis a reaction to the heavyweight complexity in the mainstreamJ2EE world, but much of it is also exploring alternatives and comingup with creative ideas. A common issue to deal with is how to wiretogether different elements: how do you fit together this webcontroller architecture with that database interface backing when theywere built by different teams with little knowledge of each other. Anumber of frameworks have taken a stab at this problem, and severalare branching out to provide a general capability to assemblecomponents from different layers. These are often referred to aslightweight containers, examples include PicoContainer, and Spring.

Underlying these containers are a number of interesting designprinciples, things that go beyond both these specific containers andindeed the Java platform. Here I want to start exploring some of theseprinciples. The examples I use are in Java, but like most of mywriting the principles are equally applicable to other OOenvironments, particularly .NET.


Components and Services

The topic of wiring elements together drags me almostimmediately into the knotty terminology problems that surround theterms service and component. You find long and contradictory articleson the definition of these things with ease. For my purposes here aremy current uses of these overloaded terms.

I use component to mean a glob of software that's intended tobe used, without change, by an application that is out of the control ofthe writers of the component. By 'without change' I mean that theusing application doesn't change the source code of the components,although they may alter the component's behavior by extending it inways allowed by the component writers.

A service is similar to a component in that it's used byforeign applications. The main difference is that I expect a componentto be used locally (think jar file, assembly, dll, or a sourceimport). A service will be used remotely through some remoteinterface, either synchronous or asynchronous (eg web service,messaging system, RPC, or socket.)

I mostly use service in this article, but much of the samelogic can be applied to local components too. Indeed often you needsome kind of local component framework to easily access a remoteservice. But writing "component or service" is tiring to read andwrite, and services are much more fashionable at the moment.


A Naive Example

To help make all of this more concrete I'll use a runningexample to talk about all of this. Like all of my examples it's one ofthose super-simple examples; small enough to be unreal, but hopefullyenough for you to visualize what's going on without falling into thebog of a real example.

In this example I'm writing a component that provides a list ofmovies directed by a particular director. This stunningly usefulfunction is implemented by a single method.

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()]);  }

The implementation of this function is naive in the extreme, itasks a finder object (which we'll get to in a moment) to return everyfilm it knows about. Then it just hunts through this list to returnthose directed by a particular director. This particular piece ofnaivety I'm not going to fix, since it's just the scaffolding for thereal point of this article.

The real point of this article is this finder object, orparticularly how we connect the lister object with a particular finderobject. The reason why this is interesting is that I want my wonderfulmoviesDirectedBy method to be completely independent ofhow all the movies are being stored. So all the method does is referto a finder, and all that finder does is know how to respond to thefindAll method. I can bring this out by defining aninterface for the finder.

public interface MovieFinder {    List findAll();}

Now all of this is very well decoupled, but at some point Ihave to come up with a concrete class to actually come up with themovies. In this case I put the code for this in the constructor of mylister class.

class MovieLister...

  private MovieFinder finder;  public MovieLister() {    finder = new ColonDelimitedMovieFinder("movies1.txt");  }

The name of the implementation class comes from the fact thatI'm getting my list from a colon delimited text file. I'll spare youthe details, after all the point is just that there's someimplementation.

Now if I'm using this class for just myself, this is all fineand dandy. But what happens when my friends are overwhelmed by adesire for this wonderful functionality and would like a copy of myprogram? If they also store their movie listings in a colon delimitedtext file called "movies1.txt" then everything is wonderful. If theyhave a different name for their movies file, then it's easy to put thename of the file in a properties file. But what if they have acompletely different form of storing their movie listing: a SQLdatabase, an XML file, a web service, or just another format of textfile? In this case we need a different class to grab that data. Nowbecause I've defined aMovieFinder interface, this won'talter my moviesDirectedBy method. But I still need tohave some way to get an instance of the right finder implementationinto place.

Figure 1: The dependencies using a simple creationin the lister class

Figure 1 shows the dependencies for thissituation. TheMovieLister class is dependent on both theMovieFinder interface and upon the implementation. Wewould prefer it if it were only dependent on the interface, but thenhow do we make an instance to work with?

In my book P of EAA, wedescribed this situation as a Plugin. Theimplementation class for the finder isn't linked into the program atcompile time, since I don't know what my friends are going to use.Instead we want my lister to work with any implementation, and forthat implementation to be plugged in at some later point, out of myhands. The problem is how can I make that link so that my lister classis ignorant of the implementation class, but can still talk to aninstance to do its work.

Expanding this into a real system, we might have dozens of suchservices and components. In each case we can abstract our use of thesecomponents by talking to them through an interface (and using anadapter if the component isn't designed with an interface in mind).But if we wish to deploy this system in different ways, we need to useplugins to handle the interaction with these services so we can usedifferent implementations in different deployments.

So the core problem is how do we assemble these plugins into anapplication? This is one of the main problems that this new breed oflightweight containers face, and universally they all do it usingInversion of Control.


Inversion of Control

When these containers talk about how they are so useful becausethey implement "Inversion of Control" I end up very puzzled.Inversionof control is a common characteristic of frameworks, so saying thatthese lightweight containers are special because they use inversion ofcontrol is like saying my car is special because it has wheels.

The question is: "what aspect of control are they inverting?"When I first ran into inversion of control, it was in the main controlof a user interface. Early user interfaces were controlled by theapplication program. You would have a sequence of commands like "Entername", "enter address"; your program would drive the prompts and pickup a response to each one. With graphical (or even screen based) UIsthe UI framework would contain this main loop and your program insteadprovided event handlers for the various fields on the screen. The maincontrol of the program was inverted, moved away from you to theframework.

For this new breed of containers the inversion is about howthey lookup a plugin implementation. In my naive example the listerlooked up the finder implementation by directly instantiating it. Thisstops the finder from being a plugin. The approach that thesecontainers use is to ensure that any user of a plugin follows someconvention that allows a separate assembler module to inject theimplementation into the lister.

As a result I think we need a more specific name for thispattern. Inversion of Control is too generic a term, and thus peoplefind it confusing. As a result with a lot of discussion with variousIoC advocates we settled on the nameDependency Injection.

I'm going to start by talking about the various forms ofdependency injection, but I'll point out now that that's not the only wayof removing the dependency from the application class to the pluginimplementation. The other pattern you can use to do this is ServiceLocator, and I'll discuss that after I'm done with explaining DependencyInjection.


Forms of Dependency Injection

The basic idea of the Dependency Injection is to have a separateobject, an assembler, that populates a field in the lister class withan appropriate implementation for the finder interface, resulting in adependency diagram along the lines ofFigure 2

Figure 2: The dependencies for a DependencyInjector

There are three main styles of dependency injection. The names I'musing for them are Constructor Injection, Setter Injection, andInterface Injection. If you read about this stuff in the currentdiscussions about Inversion of Control you'll hear these referred toas type 1 IoC (interface injection), type 2 IoC (setter injection) andtype 3 IoC (constructor injection). I find numeric names rather hardto remember, which is why I've used the names I have here.

Constructor Injection with PicoContainer

I'll start with showing how this injection is done using alightweight container calledPicoContainer. I'm starting here primarilybecause several of my colleagues at ThoughtWorks are very active in thedevelopment of PicoContainer (yes, it's a sort of corporatenepotism.)

PicoContainer uses a constructor to decide how to inject afinder implementation into the lister class. For this to work, themovie lister class needs to declare a constructor that includeseverything it needs injected.

class MovieLister...

  public MovieLister(MovieFinder finder) {      this.finder = finder;         }

The finder itself will also be managed by the pico container,and as such will have the filename of the text file injected into itby the container.

class ColonMovieFinder...

  public ColonMovieFinder(String filename) {      this.filename = filename;  }

The pico container then needs to be told which implementationclass to associate with each interface, and which string to injectinto the finder.

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;}

This configuration code is typically set up in a differentclass. For our example, each friend who uses my lister might write theappropriate configuration code in some setup class of their own. Ofcourse it's common to hold this kind of configuration information inseparate config files. You can write a class to read a config file andset up the container appropriately. Although PicoContainer doesn'tcontain this functionality itself, there is a closely related projectcalled NanoContainer that provides the appropriate wrappers to allowyou to have XML configuration files. Such a nano container will parsethe XML and then configure an underlying pico container. Thephilosophy of the project is to separate the config file format fromthe underlying mechanism.

To use the container you write code something like this.

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());}

Although in this example I've used constructor injection,PicoContainer also supports setter injection, although itsdevelopers do prefer constructor injection.

Setter Injection with Spring

The Spring framework isa wide ranging framework for enterprise Java development. It includesabstraction layers for transactions, persistence frameworks, webapplication development and JDBC. Like PicoContainer it supports bothconstructor and setter injection, but its developers tend to prefersetter injection - which makes it an appropriate choice for this example.

To get my movie lister to accept the injection I define asetting method for that service

class MovieLister...

  private MovieFinder finder;public void setFinder(MovieFinder finder) {  this.finder = finder;}

Similarly I define a setter for the filename.

class ColonMovieFinder...

  public void setFilename(String filename) {      this.filename = filename;  }

The third step is to set up the configuration for the files.Spring supports configuration through XML files and also through code,but XML is the expected way to do it.

<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>

The test then looks like this.

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

The third injection technique is to define and use interfacesfor the injection.Avalon isan example of a framework that uses thistechnique in places. I'll talk a bit more about that later, butin this case I'm going to use it with some simple sample code.

With this technique I begin by defining an interface thatI'll use to perform the injection through. Here's the interface forinjecting a movie finder into an object.

public interface InjectFinder {    void injectFinder(MovieFinder finder);}

This interface would be defined by whoever provides theMovieFinder interface. It needs to be implemented by any class thatwants to use a finder, such as the lister.

class MovieLister implements InjectFinder

  public void injectFinder(MovieFinder finder) {      this.finder = finder;  }

I use a similar approach to inject the filename into thefinder implementation.

public interface InjectFinderFilename {    void injectFilename (String filename);}

class ColonMovieFinder implements MovieFinder, InjectFinderFilename...

  public void injectFilename(String filename) {      this.filename = filename;  }

Then, as usual, I need some configuration code to wire up theimplementations. For simplicity's sake I'll do it in code.

class Tester...

  private Container container;   private void configureContainer() {     container = new Container();     registerComponents();     registerInjectors();     container.start();  }

This configuration has two stages, registering componentsthrough lookup keys is pretty similar to the other examples.

class Tester...

  private void registerComponents() {    container.registerComponent("MovieLister", MovieLister.class);    container.registerComponent("MovieFinder", ColonMovieFinder.class);  }

A new step is to register the injectors that will inject thedependent components. Each injection interface needs some codeto inject the dependent object. Here I do this by registeringinjector objects with the container. Each injector objectimplements the injector interface.

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);}

When thedependent is a class written for this container, it makes sense for thecomponent to implement the injector interface itself, as I do here with themovie finder. For generic classes, such as the string, I use aninner class within the configuration code.

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");          }    }

The tests then use the container.

class Tester…

  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());  }

The container uses thedeclared injection interfaces to figure out the dependenciesand the injectors to inject the correct dependents. (Thespecific container implementation I did here isn't important tothe technique, and I won't show it because you'd only laugh.)


Using a Service Locator

The key benefit of a Dependency Injector is that it removes thedependency that theMovieLister class has on the concreteMovieFinder implementation. This allows me to givelisters to friends and for them to plug in a suitable implementationfor their own environment. Injection isn't the only way to break thisdependency, another is to use a service locator.

The basic idea behind a service locator is to have an objectthat knows how to get hold of all of the services that an applicationmight need. So a service locator for this application would have amethod that returns a movie finder when one is needed. Of course thisjust shifts the burden a tad, we still have to get the locator intothe lister, resulting in the dependencies ofFigure 3

Figure 3: The dependencies for a ServiceLocator

In this case I'll use the ServiceLocator as a singleton Registry.The lister can then use that to get the finder when it'sinstantiated.

class MovieLister...

  MovieFinder finder = ServiceLocator.movieFinder();

class ServiceLocator...

  public static MovieFinder movieFinder() {      return soleInstance.movieFinder;  }  private static ServiceLocator soleInstance;  private MovieFinder movieFinder;

As with the injection approach, we have to configure theservice locator. Here I'm doing it in code, but it's not hard to use amechanism that would read the appropriate data from a configurationfile.

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;  }

Here's the test 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());  }

I've often heard the complaint that these kinds of servicelocators are a bad thing because they aren't testable because youcan't substitute implementations for them. Certainly you can designthem badly to get into this kind of trouble, but you don't have to. Inthis case the service locator instance is just a simple data holder.I can easily create the locator with test implementations of myservices.

For a more sophisticated locator I can subclass service locatorand pass that subclass into the registry's class variable. I canchange the static methods to call a method on the instance rather thanaccessing instance variables directly. I can provide thread–specificlocators by using thread–specific storage. All of this can be donewithout changing clients of service locator.

A way to think of this is that service locator is a registrynot a singleton. A singleton provides a simple way of implementing aregistry, but that implementation decision is easily changed.

Using a Segregated Interface for the Locator

One of the issues with the simple approach above, is that theMovieLister is dependent on the full service locator class, eventhough it only uses one service. We can reduce this by using arole interface. That way, instead of using the full servicelocator interface, the lister can declare just the bit of interface itneeds.

In this situation the provider of the lister would alsoprovide a locator interface which it needs to get hold of thefinder.

public interface MovieFinderLocator {    public MovieFinder movieFinder();

The locator then needs to implement this interface to provideaccess to a 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;

You'll notice that since we want to use an interface, wecan't just access the services through static methods any more. Wehave to use the class to get a locator instance and then use that toget what we need.

A Dynamic Service Locator

The above example was static, in that the service locatorclass has methods for each of the services that you need. This isn'tthe only way of doing it, you can also make a dynamic service locatorthat allows you to stash any service you need into it and make yourchoices at runtime.

In this case, the service locator uses a map instead offields for each of the services, and provides generic methods to getand load services.

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);  }

Configuring involves loading a service with an appropriatekey.

class Tester...

  private void configure() {      ServiceLocator locator = new ServiceLocator();      locator.loadService("MovieFinder", new ColonMovieFinder("movies1.txt"));      ServiceLocator.load(locator);  }

I use the service by using the same key string.

class MovieLister...

  MovieFinder finder = (MovieFinder) ServiceLocator.getService("MovieFinder");

On the whole I dislike this approach. Although it's certainlyflexible, it's not very explicit. The only way I can find out how toreach a service is through textual keys. I prefer explicit methodsbecause it's easier to find where they are by looking at the interfacedefinitions.

Using both a locator and injection with Avalon

Dependency injection and a service locator aren't necessarilymutually exclusive concepts. A good example of using bothtogether is the Avalon framework. Avalon uses a service locator,but uses injection to tell components where to find the locator.

Berin Loritsch sent me this simple version of myrunning example using Avalon.

public class MyMovieLister implements MovieLister, Serviceable {    private MovieFinder finder;    public void service( ServiceManager manager ) throws ServiceException {        finder = (MovieFinder)manager.lookup("finder");    }       

The service method is an example of interface injection,allowing the container to inject a service manager intoMyMovieLister. The service manager is an example of a servicelocator. In this example the lister doesn't store the manager ina field, instead it immediately uses it to lookup the finder,which it does store.


Deciding which option to use

So far I've concentrated on explaining how I see these patternsand their variations. Now I can start talking about their pros andcons to help figure out which ones to use and when.

Service Locator vs Dependency Injection

The fundamental choice is between Service Locator and DependencyInjection. The first point is that both implementations provide thefundamental decoupling that's missing in the naive example - in bothcases application code is independent of the concrete implementationof the service interface. The important difference between the twopatterns is about how that implementation is provided to theapplication class. With service locator the application class asks forit explicitly by a message to the locator. With injection there is noexplicit request, the service appears in the application class - hencethe inversion of control.

Inversion of control is a common feature of frameworks, butit's something that comes at a price. It tends to be hard tounderstand and leads to problems when you are trying to debug. So onthe whole I prefer to avoid it unless I need it. This isn't to sayit's a bad thing, just that I think it needs to justify itself overthe more straightforward alternative.

The key difference is that with a Service Locator every userof a service has a dependency to the locator. The locator can hidedependencies to other implementations, but you do need to see thelocator. So the decision between locator and injector depends onwhether that dependency is a problem.

Using dependency injection can help make it easier to see what thecomponent dependencies are. With dependency injector you can just look atthe injection mechanism, such as the constructor, and see thedependencies. With the service locator you have to search the sourcecode for calls to the locator. Modern IDEs with a find referencesfeature make this easier, but it's still not as easy as looking at theconstructor or setting methods.

A lot of this depends on the nature of the user of theservice. If you are building an application with various classes thatuse a service, then a dependency from the application classes to thelocator isn't a big deal. In my example of giving a Movie Lister to myfriends, then using a service locator works quite well. All they needto do is to configure the locator to hook in the right serviceimplementations, either through some configuration code or through aconfiguration file. In this kind of scenario I don't see theinjector's inversion as providing anything compelling.

The difference comes if the lister is a component that I'mproviding to an application that other people are writing. In thiscase I don't know much about the APIs of the service locators that my customersare going to use. Each customer might have their own incompatibleservice locators. I can get around some of this by using thesegregated interface. Each customer can write an adapter that matchesmy interface to their locator, but in any case I still need to see thefirst locator to lookup my specific interface. And once the adapterappears then the simplicity of the direct connection to a locator isbeginning to slip.

Since with an injector you don't have a dependency from acomponent to the injector, the component cannot obtain furtherservices from the injector once it's been configured.

A common reason people give for preferring dependency injectionis that it makes testing easier. The point here is that to do testing,you need to easily replace real service implementations with stubs ormocks. However there is really no difference here between dependencyinjection and service locator: both are very amenable to stubbing. Isuspect this observation comes from projects where people don't makethe effort to ensure that their service locator can be easilysubstituted. This is where continual testing helps, if you can'teasily stub services for testing, then this implies a serious problemwith your design.

Of course the testing problem is exacerbated by componentenvironments that are very intrusive, such as Java's EJB framework. Myview is that these kinds of frameworks should minimize their impactupon application code, and particularly should not do things that slowdown the edit-execute cycle. Using plugins to substitute heavyweightcomponents does a lot to help this process, which is vital for practicessuch as Test Driven Development.

So the primary issue is for people who are writing code thatexpects to be used in applications outside of the control of thewriter. In these cases even a minimal assumption about a ServiceLocator is a problem.

Constructor versus Setter Injection

For service combination, you always have to have someconvention in order to wire things together. The advantage ofinjection is primarily that it requires very simple conventions - atleast for the constructor and setter injections. You don'thave to do anything odd in your component and it's fairlystraightforward for an injector to get everything configured.

Interface injectionis more invasive since you have to write a lot of interfaces to getthings all sorted out. For a small set of interfaces required by thecontainer, such as in Avalon's approach, this isn't too bad. But it'sa lot of work for assembling components and dependencies, which is whythe current crop of lightweight containers go with setter andconstructor injection.

The choice between setter and constructor injection isinteresting as it mirrors a more general issue with object-orientedprogramming - should you fill fields in a constructor or withsetters.

My long running default with objects is as much as possible,to create valid objects at construction time. This advice goes back toKent Beck'sSmalltalk Best Practice Patterns: Constructor Method andConstructor Parameter Method. Constructors with parameters give you aclear statement of what it means to create a valid object in anobvious place. If there's more than one way to do it, create multipleconstructors that show the different combinations.

Another advantage with constructor initialization is that itallows you to clearly hide any fields that are immutable by simply notproviding a setter. I think this is important - if something shouldn'tchange then the lack of a setter communicates this very well. If youuse setters for initialization, then this can become a pain. (Indeedin these situations I prefer to avoid the usual setting convention,I'd prefer a method likeinitFoo, to stress that it'ssomething you should only do at birth.)

But with any situation there are exceptions. If you have alot of constructor parameters things can look messy, particularly inlanguages without keyword parameters. It's true that a longconstructor is often a sign of an over-busy object that should besplit, but there are cases when that's what you need.

If you have multiple ways to construct a valid object, it canbe hard to show this through constructors, since constructors can onlyvary on the number and type of parameters. This is when FactoryMethods come into play, these can use a combination of privateconstructors and setters to implement their work. The problem withclassic Factory Methods for components assembly is that they areusually seen as static methods, and you can't have those oninterfaces. You can make a factory class, but then that just becomesanother service instance. A factory service is often a good tactic,but you still have to instantiate the factory using one of thetechniques here.

Constructors also suffer if you have simple parameters suchas strings. With setter injection you can give each setter a name toindicate what the string is supposed to do. With constructors you arejust relying on the position, which is harder to follow.

If you have multiple constructors and inheritance, thenthings can get particularly awkward. In order to initialize everythingyou have to provide constructors to forward to each superclassconstructor, while also adding you own arguments. This can lead to aneven bigger explosion of constructors.

Despite the disadvantages my preference is to start withconstructor injection, but be ready to switch to setter injection assoon as the problems I've outlined above start to become a problem.

This issue has led to a lot of debate between the variousteams who provide dependency injectors as part of theirframeworks. However it seems that most people who build theseframeworks have realized that it's important to support bothmechanisms, even if there's a preference for one of them.

Code or configuration files

A separate but often conflated issue is whether to useconfiguration files or code on an API to wire up services. For mostapplications that are likely to be deployed in many places, a separateconfiguration file usually makes most sense. Almost all the time thiswill be an XML file, and this makes sense. However there are caseswhere it's easier to use program code to do the assembly. One case iswhere you have a simple application that's not got a lot of deploymentvariation. In this case a bit of code can be clearer than a separate XMLfile.

A contrasting case is where the assembly is quite complex,involving conditional steps. Once you start getting close toprogramming language then XML starts breaking down and it's better touse a real language that has all the syntax to write a clear program.You then write a builder class that does the assembly. If you havedistinct builder scenarios you can provide several builder classes anduse a simple configuration file to select between them.

I often think that people are over-eager to defineconfiguration files. Often a programming language makes astraightforward and powerful configuration mechanism. Modern languagescan easily compile small assemblers that can be used to assembleplugins for larger systems. If compilation is a pain, then there arescripting languages that can work well also.

It's often said that configuration files shouldn't use aprograming language because they need to be edited by non-programmers.But how often is this the case? Do people really expectnon-programmers to alter the transaction isolation levels of a complexserver-side application? Non-language configuration files work wellonly to the extent they are simple. If they become complex then it'stime to think about using a proper programming language.

One thing we're seeing in the Java world at the moment is acacophony of configuration files, where every component has its ownconfiguration files which are different to everyone else's. If you usea dozen of these components, you can easily end up with a dozenconfiguration files to keep in sync.

My advice here is to always provide a way to do allconfiguration easily with a programmatic interface, and then treat aseparate configuration file as an optional feature. You can easilybuild configuration file handling to use the programmatic interface.If you are writing a component you then leave it up to your userwhether to use the programmatic interface, your configuration fileformat, or to write their own custom configuration file format and tieit into the programmatic interface

Separating Configuration from Use

The important issue in all of this is to ensure that theconfiguration of services is separated from their use. Indeed this isa fundamental design principle that sits with the separation ofinterfaces from implementation. It's something we see within anobject-oriented program when conditional logic decides which class toinstantiate, and then future evaluations of that conditional are donethrough polymorphism rather than through duplicated conditionalcode.

If this separation is useful within a single code base, it'sespecially vital when you're using foreign elements such as componentsand services. The first question is whether you wish to defer thechoice of implementation class to particular deployments. If so youneed to use some implementation of plugin. Once you are using pluginsthen it's essential that the assembly of the plugins is doneseparately from the rest of the application so that you can substitutedifferent configurations easily for different deployments. How youachieve this is secondary. This configuration mechanism can eitherconfigure a service locator, or use injection to configure objectsdirectly.


Some further issues

In this article, I've concentrated on the basic issues ofservice configuration using Dependency Injection and Service Locator.There are some more topics that play into this which also deserveattention, but I haven't had time yet to dig into. In particular thereis the issue of life-cycle behavior. Some components have distinctlife-cycle events: stop and starts for instance. Another issue is thegrowing interest in using aspect oriented ideas with these containers.Although I haven't considered this material in the article at themoment, I do hope to write more about this either by extending thisarticle or by writing another.

You can find out a lot more about these ideas by looking at theweb sites devoted to the lightweight containers. Surfing from thepicocontainer andspring web sites will lead to you intomuch more discussion of these issues and a start on some of thefurther issues.


Concluding Thoughts

The current rush of lightweight containers all have a commonunderlying pattern to how they do service assembly - the dependencyinjector pattern. Dependency Injection is a useful alternativeto Service Locator. When building application classes the two areroughly equivalent, but I think Service Locator has a slight edge dueto its more straightforward behavior. However if you are buildingclasses to be used in multiple applications then Dependency Injection is abetter choice.

If you use Dependency Injection there are a number of styles tochoose between. I would suggest you follow constructor injectionunless you run into one of the specific problems with thatapproach, in which case switch to setter injection. If you arechoosing to build or obtain a container, look for one that supportsboth constructor and setter injection.

The choice between Service Locator and Dependency Injection is lessimportant than the principle of separating service configuration fromthe use of services within an application.

阅读全文
0 0