inversion of control(控制反转)

原文出处
 
 
此片文章在网上已有译文,这些模式我经常是学了就忘,今天拿出来还是自己动手翻译片断以强化
 

Inversion of Control

When these containers talk about how they are so useful because they implement "Inversion of Control" I end up very puzzled. Inversion of control is a common characteristic of frameworks, so saying that these lightweight containers are special because they use inversion of control 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 control of a user interface. Early user interfaces were controlled by the application program. You would have a sequence of commands like "Enter name", "enter address"; your program would drive the prompts and pick up a response to each one. With graphical (or even screen based) UIs the UI framework would contain this main loop and your program instead provided event handlers for the various fields on the screen. The main control of the program was inverted, moved away from you to the framework.

For this new breed of containers the inversion is about how they lookup a plugin implementation. In my naive example the lister looked up the finder implementation by directly instantiating it. This stops the finder from being a plugin. The approach that these containers use is to ensure that any user of a plugin follows some convention that allows a separate assembler module to inject the implementation into the lister.

As a result I think we need a more specific name for this pattern. Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various IoC advocates we settled on the name Dependency Injection.

I'm going to start by talking about the various forms of dependency injection, but I'll point out now that that's not the only way of removing the dependency from the application class to the plugin implementation. The other pattern you can use to do this is Service Locator, and I'll discuss that after I'm done with explaining Dependency Injection.


 
译:当这些轻量级容器的作者提及这些容器是如何有用是因为他们实现了控制反转的时候,我就觉得很迷惑,控制反转只是容器的一个普通特征,因为实现了控制反转而说这些轻量级容器是如何与众不同就好像有人说我的汽车有轮子,所以我的汽车很特别一样。
 
问题是这些容器反转了那方面的控制?我第一次遇到控制反转是在用户界面的设计上,早期的用户界面是由应用程序来控制的,你预先设计一系列命令如:“输入姓名”,“输入地址”;应用程序将逐条输出提示信息并接受你对每一条信息给出的应答。而采用了图形的用户界面后,界面框架将负责运行一个主循环,你的应用程序只是提供针对屏幕的事件处理。这里程序的控制被反转了,不再需要你的应用程序来控制,而是由框架来控制。
 
 
对于这些新生的容器,反转就是关于“容器如何定位一个插件的具体实现”,在我以上的例子中,lister通过直接实例化来定位一个finder的实现。这种办法使得finder不能成为一个插件,因为finder不是在运行时插入应用程序的,这些容器提供的办法就是确保插件用户遵循一些约定,只要遵守这些约定,一个独立的配置模块就可以向应用程序注射入lister的具体实现。
 
因此,我想给这个模式一个更为特别的名字,控制反转是太普通的一个术语,人们很容易混淆,经过多次讨论,各种不同的控制反转的提倡者和我都同意使用一个新名字 -----依赖注入。
 
下面就开始讨论依赖注入的各种形式,但是现在我要指出的是依赖注入并不是把依赖从应用程序类移出到插件的唯一办法,另外一种办法就是使用服务定位,这要等到我讨论完依赖注入后再来解释。


Forms of Dependency Injection

The basic idea of the Dependency Injection is to have a separate object, an assembler, that populates a field in the lister class with an appropriate implementation for the finder interface, resulting in a dependency diagram along the lines of Figure 2

Figure 2: The dependencies for a Dependency Injector

There are three main styles of dependency injection. The names I'm using for them are Constructor Injection, Setter Injection, and Interface Injection. If you read about this stuff in the current discussions about Inversion of Control you'll hear these referred to as type 1 IoC (interface injection), type 2 IoC (setter injection) and type 3 IoC (constructor injection). I find numeric names rather hard to remember, which is why I've used the names I have here.


 

依赖注射的形式

依赖注入最基本的思想就是用一个单独的对象,即装配器来获得Moviefinder接口的合适的实现,并将其实例赋给Movielister类的一个字段,图2展示的就是这样的一个依赖关系图。

一般有三种形式的依赖注入。我这里给他们分别命名为构造函数注入,设置方法注入和接口注入。假如你读过一些最近关于讨论控制反转的一些内容,你可能听说过这些材料所指的类型1,类型2和类型3。我发现数字名称相当难记,所以我这里使用了上述好记的名称。


Constructor Injection with PicoContainer

I'll start with showing how this injection is done using a lightweight container called PicoContainer. I'm starting here primarily because several of my colleagues at ThoughtWorks are very active in the development of PicoContainer (yes, it's a sort of corporate nepotism.)

PicoContainer uses a constructor to decide how to inject a finder implementation into the lister class. For this to work, the movie lister class needs to declare a constructor that includes everything 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 it by the container.

class ColonMovieFinder...
    public ColonMovieFinder(String filename) {
        this.filename = filename;
}
  
  
使用picoContainer进行构造函数注入
我下面将使用一种叫做picocontainer的轻量级容器来演示这种注入是如何完成的。这样做主要是因为我的几个在thoughtworks的同事在picocontainer开发方面非常
活跃,(没错,这也算是一种偏袒吧)。
picocontainer使用构造函数来决定如何向MovieLister类注入MovieFinder的实现,为了使picocontainer工作,Movie Lister类需要声明一个构造器,在其中需要包含它要注入
的所有东西。
class MovieLister...
    public MovieLister(MovieFinder finder){
        this.finder=finder;
}
Moviefinder本身也将由pico container来管理,同样,文本文件的文件名也将由容器来注入finder类。
  
  
The pico container then needs to be told which implementation class to associate with each interface, and which string to inject into 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 different class. For our example, each friend who uses my lister might write the appropriate configuration code in some setup class of their own. Of course it's common to hold this kind of configuration information in separate config files. You can write a class to read a config file and set up the container appropriately. Although PicoContainer doesn't contain this functionality itself, there is a closely related project called NanoContainer that provides the appropriate wrappers to allow you to have XML configuration files. Such a nano container will parse the XML and then configure an underlying pico container. The philosophy of the project is to separate the config file format from the 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 it's developers do prefer constructor injection.


pico container容器随后将会被通知何种具体实现将被注入各个接口,什么样的文件名将被注入movie 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;
}
 
这些设置代码通常位于另一个类,对于我的这个例子,每一个使用我的movielister的朋友可能需要在它们自己的设置类中填写相应的设置代码。当然,一般来说,也可以把这种设置信息写入文件。你可以写一个类读取设置文件并且相应的安装容器,尽管picocontain本身并不包含这个功能,但另外有一个与它非常相关的叫做NanoContainer的项目提供相应的包装,允许你使用XML配置文件。这样,nano container将解析XML文件并据此设置一个pico container.这个项目的哲学观点就是将配置文件从项目的基础机制里独立出来。
 
使用这个容器,你写出的代码会是这样的.
 
 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());

 }
 
尽管这个例子我使用了构造器注入,picocontainer 也支持设置方法的注入,虽然开发者们更倾向于使用构造器注入。

   


Setter Injection with Spring

The Spring framework is a wide ranging framework for enterprise Java development. It includes abstraction layers for transactions, persistence frameworks, web application development and JDBC. Like PicoContainer it supports both constructor and setter injection, but its developers tend to prefer setter injection - which makes it an appropriate choice for this example.

To get my movie lister to accept the injection I define a setting 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;
     }
  
  
spring框架是一个被企业广泛使用的Java开发的框架。它包括了对事务处理、持久层框架、web应用和JDBC开发的抽象。就像picocontainer一样,他既支持构造器注入,也支持
也支持设置方法注入。但是他的开发者更喜欢设置方法注入-所以恰好适合这个例子。
为了让我的movie lister能够接受注入,我为它定义了一个设置方法,该设置方法接受MovieFinder为参数。
class MovieLister...
    
    private MovieFinder finder;
  
    public void setFinder(MovieFinder finder) {
    
    this.finder = finder;
  
}

 

类似的,在MovieFinder中,我也定义了一个接受文件名为参数的设置方法。

 

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());
    }
  
  
第三步就是建立设置文件,Spring既支持XML文件设置同时也支持代码设置,不过,XML是比较理想的设置方式。
<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>
测试代码可能看上去会像这样
 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 interfaces for the injection. Avalon is an example of a framework that uses this technique in places. I'll talk a bit more about that later, but in this case I'm going to use it with some simple sample code.

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

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

This interface would be defined by whoever provides the MovieFinder interface. It needs to be implemented by any class that wants 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 the finder implementation.

public interface InjectFinderFilename {
    void injectFilename (String filename);
}
class ColonMovieFinder implements MovieFinder, InjectFinderFilename......
    public void injectFilename(String filename) {
        this.filename = filename;
}
  
  
接口注入
第三种注入技术就是定义和使用接口注入. Avalon就是某些地方使用了该种技术的一个框架例子,关于该种注入技术,稍后我要谈论得更多一点,但本例中我将通过一些
简单的代码来使用它。
使用这种技术时将首先定义一个接口,我将通过该接口执行注入。下面这个例子就是把Movie Finder实例注入了一个实现该接口的对象的例子。
public interface InjectFinder {
   
 void injectFinder(MovieFinder finder);
}

 

上面这个接口将由提供MovieFinder接口的人来定义。任何使用movie finder的类都必须实现实现该接口,如以下movie lister.

 

class MovieLister implements InjectFinder...

    public void injectFinder(MovieFinder finder) {

    this.finder = finder;

 

 

 

 

}

我也使用了同样的方法把文件名注入movie finder的实现类。
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 the implementations. 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 components through lookup keys is pretty similar to the other examples.

class Tester...
  private void registerComponents() {
    container.registerComponent("MovieLister", MovieLister.class);
    container.registerComponent("MovieFinder", ColonMovieFinder.class);
  }
   
   
然后,就像通常所做的那样,我也需要一些设置代码来装配所有的实现。为简单起见,我直接在代码中完成配置。
class Tester...
    private Container container;

     private void configureContainer() {
       container = new Container();
       registerComponents();
       registerInjectors();
       container.start();
}
这个设置分成注册部件和注册注入器两个步骤。通过查询关键字注册部件和其他例子的代码非常相似。
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"));
//自己解释:通过这样的实现,再看下文ColorMovieFinder实现了Injecotor,Injector的实现其实是注册了依赖类(movie finder),因为每个依赖类都是调用原类
//(movie lister)来执行注入的,依赖类实现了Injector接口,传递了this到原类。
    container.registerInjector(InjectFinderFilename.class, new FinderFilenameInjector());
  }
public interface Injector {
  public void inject(Object target);
}
   
   
下面一步就是注册注入器对象,该注入器对象将注入依赖的部件。每一个注入器接口需要一些代码来注入依赖的部件。这儿我通过向容器注册注入器对象来实现。每一个注入器
对象实现了injector接口。
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");      
    }
}
   
   
当被注入的依赖部件本身是为容器而所写的一个类的时候,让该依赖部件本身实现injector接口更有意义,就像我这儿的movie finder,他本身实现了Injector接口.对于一般的类,
象string,在设置代码中我使用了内部类。
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.)


最后使用容器的测试就变成了这样。
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());

    }
容器将使用movieLister已经声明的injection接口计算出他所有的依赖和注入依赖的注入器。
(与容器相关的具体实现对于IOC来说并不重要,我要再演示的话你就要笑了。)
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值