次の方法で共有


Provider Injection

In his article Inversion of Control Containers and the Dependency Injection pattern, Martin Fowler describes four types of inversion of control:

  • Constructor Injection
  • Property Injection
  • Interface Injection
  • Service Locator

Constructor injection and service locator are, in my opinion, the most attractive patterns for addressing the challenge of configuring a component's servers in a flexible manner. They both have advantages and disadvantages, but a third way exists, which I call provider injection.

As usual, a simplistic example will serve to illustrate my points. Assume that you need to use this server in a client:

 public abstract class MyServer
 {
     protected MyServer()
     {
     }
  
     public abstract string DoStuff(string message);
 }

Using the constructor injection pattern, a client might look like this:

 public class MyClient1
 {
     private MyServer server_;
  
     public MyClient1(MyServer server)
     {
         if (server == null)
         {
             throw new ArgumentNullException("server");
         }
         this.server_ = server;
     }
  
     public string GetServerMessage()
     {
         return this.server_.DoStuff("ploeh");
     }
 }

This approach is robust and easy to understand. It does, however, also come with a disadvantage: A client of MyClass1 must always supply the required dependency. For a class which will always use the dependency heavily, this may not be a problem, but if the dependency is only used in specialized situations, this may incur an initialization overhead. If you have an entire hierarchy of clients and services, using this pattern requires you to initialize all dependencies immediately, which may lead to increased start-up time.

The serivce locator pattern is typically implemented by using a registry that can be queried for dependencies; often in the form of a factory class. An client utilizing this pattern might look like this:

 public class MyClient2
 {
     public MyClient2()
     {
     }
  
     public string GetServerMessage()
     {
         MyServer server = ServiceFactory.Create<MyServer>();
  
         return server.DoStuff("ploeh");
     }
 }

(I'm using Service Locator 2 here, but other lightweight container frameworks such as Spring.NET work in essentially the same way.)

Contrary to constructor injection, this implementation doesn't suffer from the early initialization problem: If the first call to GetServerMessage occurs five minutes into the application's lifetime, the MyServer instance will not be created before then. In other words, this pattern supports Just-in-Time (JiT) initialization of dependencies.

On the other hand, the usability of MyClient2 is not nearly as good as MyClient1. In the first example, any caller of MyClient1 must supply the dependency. The API communicates very clearly what you must do to use the class. On the other hand, you could easily write code utilizing MyClient2 like this:

 MyClient2 mc = new MyClient2();
  
 string result = mc.GetServerMessage();

This code snippet will compile, but you will get a run-time error if ServiceFactory can't create the requested instance, which may very well be the case. To make the application work, it's neccessary to configure the service locator (in this case ServiceFactory) either by app.config files or somewhere else in the code. Although configuration-driven applications may be desirable for a lot of reasons, it doesn't make usability of the API better. If you forget to configure the service locator, you will have to resort to the documentation to figure out what to do, since the API itself offers no help in this regard.

Provider Injection attempts to achieve the best of both worlds:

  • Enabling API design that clearly communicates dependency requirements
  • Enabling JiT initialization of dependencies

Here's an example of a class utilizing provider injection:

 public class MyClient3
 {
     private ServiceProvider<MyServer> provider_;
  
     public MyClient3(ServiceProvider<MyServer> provider)
     {
         if (provider == null)
         {
             throw new ArgumentNullException("provider");
         }
         this.provider_ = provider;
     }
  
     public string GetServerMessage()
     {
         MyServer server = this.provider_.Create();
  
         return server.DoStuff("ploeh");
     }
 }

(Again, I'm using Service Locator 2 as my example, but instead of ServiceProvider<T>, you could use System.IServiceProvider or some other, similar type.)

Using the provider injection pattern, the provider must be supplied by any caller of MyClient3, but the dependency itself isn't created before it is needed. The API is still rather explicit about its expectations, since it requres you to supply the provider, so it leaves less room for error.

A variant of this pattern is to require the provider as an input parameter to a method. A lot of classes in the .NET framework uses this approach, particularly in the design-time framework (System.ComponentModel.Design namespace).

If you are interested in a less simplistic implementation using this approach, you can take a look at the StarShip QuickStart sample in the Service Locator 2 application block, which creates a complex hierarchy of dependencies.

Comments

  • Anonymous
    March 26, 2007
    PingBack from http://liangwu.wordpress.com/2007/03/26/interesting-finding-03262007/

  • Anonymous
    May 11, 2007
    This is the first in a small series of posts about testing against non-determinism. In this installation,

  • Anonymous
    May 12, 2007
    This is the second in a small series of posts about testing against non-determinism. In this installation,

  • Anonymous
    May 12, 2007
    This is the third in a small series of posts about testing against non-determinism. In this installation,

  • Anonymous
    May 12, 2007
    This is the third in a small series of posts about testing against non-determinism. In this installation,

  • Anonymous
    May 12, 2007
    This is the third in a small series of posts about testing against non-determinism. In this installation,

  • Anonymous
    May 14, 2007
    This is the fourth in a small series of posts about testing against non-determinism. In this installation,

  • Anonymous
    June 02, 2007
    There are several different ways to implement Dependency Injection (DI), and Martin Fowler describes

  • Anonymous
    March 04, 2008
    Sweet! Guice does this too. http://docs.google.com/View?docid=dd2fhx4z_5df5hw8

  • Anonymous
    March 25, 2008
    Some of the new stuff in .NET 3.5 makes it much easier to implement Provider Injection . If you take