Chinaunix首页 | 论坛 | 博客

-?-

  • 博客访问: 28013
  • 博文数量: 15
  • 博客积分: 650
  • 博客等级: 上士
  • 技术积分: 135
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-31 17:43
文章分类
文章存档

2011年(1)

2009年(3)

2008年(11)

我的朋友

分类: Java

2008-12-17 23:52:09

接口注入

   除了前面两种注入技术,还可以在接口中定义需要注入的信息,并通过接口完成注入。Avalon框架就使用了类似的技术。在这里,我首先用简单的范例代码说明它的用法,后面还会有更深入的讨论。

   首先,我需要定义一个接口,组件的注入将通过这个接口进行。在本例中,这个接口的用途是将一个MovieFinder实例注入继承了该接口的对象。

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


   这个接口应该由提供MovieFinder接口的人一并提供。任何想要使用MovieFinder实例的类(例如MovieLister类)都必须实现这个接口。

class MovieLister implements InjectFinder...
    public void injectFinder(MovieFinder finder) {
        this.finder = finder;
    }


   然后,我使用类似的方法将文件名注入MovieFinder的实现类:

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

class ColonMovieFinder implements MovieFinder, InjectFinderFilename......
    public void injectFilename(String filename) {
        this.filename = filename;
    }


   现在,还需要用一些配置代码将所有的组件实现装配起来。简单起见,我直接在代码中完成配置。

class Tester...
    private Container container;

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


   这个配置分为两个步骤,就象其它例子一样,可以通过lookup keys来注册组件。

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 the dependent components. Each injection interface needs some code to inject the dependent object. Here I do this by registering injector objects with the container. Each injector object implements 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 the dependent is a class written for this container, it makes sense for the component to implement the injector interface itself, as I do here with the movie finder. For generic classes, such as the string, I use an inner 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 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());
  }


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

使用Service Locator

   依赖注入的最大好处在于:它消除了MovieLister类对具体MovieFinder实现类的依赖。这样一来,我就可以把MovieLister类交给朋友,让他们根据自己的环境插入一个合适的MovieFinder实现即可。不过,Dependency Injection模式并不是打破这层依赖关系的唯一手段,另一种方法是使用Service Locator模式。

   Service Locator模式背后的基本思想是:有一个对象(即服务定位器)知道如何获得一个应用程序所需的所有服务。也就是说,在我们的例子中,服务定位器应该有 一个方法,用于获得一个MovieFinder实例。当然,这不过是把麻烦换了一个样子,我们仍然必须在MovieLister中获得服务定位器,最终得到的依赖关系如图3所示:


图3:使用Service Locator 模式之后的依赖关系
   在这里,我把ServiceLocator类实现为一个Singleton 的注册表,于是MovieLister就可以在实例化时通过ServiceLocator获得一个MovieFinder实例。

class MovieLister...
    MovieFinder finder = ServiceLocator.movieFinder();

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


   和注入的方式一样,我们也必须对服务定位器加以配置。在这里,我直接在代码中进行配置,但设计一种通过配置文件获得数据的机制也并非难事。

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


   下面是测试代码:

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


   我时常听到这样的论调:这样的服务定位器不是什么好东 西,因为你无法替换它返回的服务实现,从而导致无法对它们进行测试。当然,如果你的设计很糟糕,你的确会遇到这样的麻烦;但你也可以选择良好的设计。在这 个例子中,ServiceLocator实例仅仅是一个简单的数据容器,只需要对它做一些简单的修改,就可以让它返回用于测试的服务实现。

   对于更复杂的情况,我可以从ServiceLocator派生出多个子类,并将子类型的 实例传递给注册表的类变量。另外,我可以修改ServiceLocator的静态方法,使其调用ServiceLocator实例的方法,而不是直接访问实例变量。我还可以使用特定于线程的存储机制,从而提供特定于线程的服务定位器。所有这一切改进都无须修改ServiceLocator的使用者。

   一种改进的思路是:服务定位器仍然是一个注册表,但不是Singleton。Singleton的确是实现注册表的一种简单途径,但这只是一个实现时的决定,可以很轻松地改变它。

为定位器提供分离的接口

   上面这种简单的实现 方式有一个问题:MovieLister类将依赖于整个ServiceLocator类,但它需要使用的却只是后者所提供的一项服务。我们可以针对这项服务提供一个单独的接口,减少MovieLister 对ServiceLocator 的依赖程度。这样一来,MovieLister 就不必使用整个的ServiceLocator接口,只需声明它想要使用的那部分接口。

   此时,MovieLister类的提供者也应该一并提供一个定位器接口,使用者可以通过这个接口获得MovieFinder实例。

public interface MovieFinderLocator {
    public MovieFinder movieFinder();


   真实的服务定位器需要实现上述接口,提供访问MovieFinder 实例的能力:

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;


   你应该已经注意到了:由于想要使用接口,我们不能再通过静态方法直接访问服务——我们必须首先通过ServiceLocator类获得定位器实例,然后使用定位器实例得到我们想要的服务。

阅读(561) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~