出处: http://www.martinfowler.com/articles/injection.html
—by Riven Huang 2007.04.25
假设有这样一个case, 需要列出某个导演的作品, 为此需要以下的class:
namespace RivenStudy
{
public class MovieLister
{
private IMovieFinder finder;
public Movie[] MoviesDirectedBy(String arg)
{
List<Movie> allMovies = finder.FindAll();
foreach(Movie movie in allMovies )
{
if (!movie.Director.Equals(arg))
allMovies.Remove(movie);
}
return (Movie[])allMovies.ToArray();
}
}
}
为了让MoiveLister不关系数据的存储方式, 引入了一个finder Object,
MovieLister会依赖于finder Object的FindAll() 方法,为此我们定义了
如下的接口:
public interface IMovieFinder
{
List<Movie> FindAll();
}
在真正的操作中,MovieLister会用到一个具体的MovieFinder比如ColonDelimitedMovieFinder,
此movie finder会从一个text文件中读取影片的列表.
在这种情况下,我们的代码会写做:
public class MovieLister
{
private IMovieFinder finder;
public MovieLister()
{
finder = new ColonDelimitedMovieFinder(”movies1.txt”);
}
public Movie[] MoviesDirectedBy(String arg)
{
List<Movie> allMovies = finder.FindAll();
foreach(Movie movie in allMovies )
{
if (!movie.Director.Equals(arg))
allMovies.Remove(movie);
}
return (Movie[])allMovies.ToArray();
}
}
我们希望MovieLister 类能够与MovieFinder的 任何 实现类协同工作,并且允许在运行期插入具体的实现类,
插入动作完全脱离原作者的控制。在Patterns of Enterprise Application Architecture
(http://www.martinfowler.com/books.html#eaa)一书中,作者称此模式为PlugIn
而此刻设计中,MovieLister 类它直接实例化IMovieFinder的具体类。这样一来,
MovieLister既依赖于IMovieFinder接口,也依赖于实现IMovieFinderde类。
IMovieFinder 也就不成其为一个插件了,因为它并不是在运行期插入应用程序中的。
通过Interface对用到的组件加以抽象,并通过Interface与具体的组件通信,(如果组件并没有设计一个接口,
也可以通过适配器与之交流),同时我们希望以PlugIn的方式以多种方式部署这个系统,所以,现在的核心问题就是:
如何将这些插件组合成一个应用程序?
这正是(lightweight containers)轻量级容器所面临的问题,解决这个问题的手段就是是控制反转(Inversion of Control)
在前面的例子中,需要翻转(Inversion)的是应用程序对插件的依赖,达到这个目的,可以使用
Dependency Injection 模式并不是唯一的选择,你也可以用ServiceLocator 模式获得同样的效果.
Dependency Injection 模式的基本思想是:
用一个单独的对象(assembler)来获得IMovieFinder的一个具体实现,并将其实例赋给MovieLister的一个字段。
Dependency Injection的三种形式:
1. Constructor Injection
2. Setter Injection
3. Interface Injection
依赖注入的最大好处在于:它消除了MovieLister类对具体MovieFinder实现类的依赖。把MovieLister 类交给辅助类,
让他们根据实际环境插入(构造)一个合适的IMovieFinder实现
。
* 使用PicoContainer (http://www.picocontainer.org/)进行构造子注入
PicoContainer 通过一个构造器来判断如何将IMovieFinder的实例注入MovieLister:
class MovieLister…
{
public MovieLister(IMovieFinder finder)
{
this.finder = finder;
}
}
IMovieFinder 实例也由PicoContainer来管理
class ColonMovieFinder
{
private string fileName;
public ColonMovieFinder(String fileName)
{
this.fileName = fileName;
}
}
随后,在一个单独的class中配置PicoContainer,说明各个接口分别与哪个实现类关联,将哪个字符串注入MovieFinder组件。
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;
}
* 使用Spring 进行设值方法注入
为了让MovieLister 类接受注入, 需要为它定义一个set方法,
class MovieLister
{
private MovieFinder finder;
public void setFinder(MovieFinder finder)
{
this.finder = finder;
}
}
在MovieFinder的实现类中,也定义了一个set方法
class ColonMovieFinder
{
public void setFilename(String filename)
{
this.filename = filename;
}
}
然后设定Spring的配置文件
<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()
{
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());
}
* 接口注入
1 . 定义一个接口,这个接口的用途是将 一个IMovieFinder实例注入该接口的实现者。
public interface InjectFinder
{
void injectFinder(MovieFinder finder);
}
这个接口应该由提供IMovieFinder 接口的人提供。任何想要使用IMovieFinder 实例的类
如MovieLister 类,都必须实现这个接口。
class MovieLister : InjectFinder
{
public void injectFinder(MovieFinder finder) {
this.finder = finder;
}
然后,我使用类似的方法将文件名注入MovieFinder的实现类:
public interface InjectFilename
{
void injectFilename (String filename);
}
class ColonMovieFinder : IMovieFinder, InjectFilename
{
public void injectFilename(String filename) {
this.filename = filename;
}
通过一些配置代码装配所有的组件并使用。
class IfaceTester
{
private MovieLister lister;
private void configureLister()
{
ColonMovieFinder finder = new ColonMovieFinder();
finder.injectFilename(”movies1.txt”);
lister = new MovieLister();
lister.injectFinder(finder);
}
测试代码则可以直接使用这个字段:
public void TestIface()
{
configureLister();
Movie[] movies = lister.moviesDirectedBy(”Sergio Leone”);
assertEquals(”Once Upon a Time in the West”, movies[0].getTitle());
}
}
使用Service Locator来进行依赖注入:
Service Locator 的基本思想是:有一个对象(即Service Locator)知道如何获得一个应用
程序所需的所有服务。即在上面的例子中,服务定位器应该有一个方法用于获得一个IMovieFinder的实例。
class MovieLister
{
…
IMovieFinder finder = ServiceLocator.movieFinder();
}
class ServiceLocator
{
private static ServiceLocator soleInstance;
private MovieFinder movieFinder;
public static IMovieFinder movieFinder()
{
return soleInstance.movieFinder;
}
public static void load(ServiceLocator arg)
{
soleInstance = arg;
}
public ServiceLocator(IMovieFinder movieFinder)
{
this.movieFinder = movieFinder;
}
}
使用
class Tester
{
private void configure()
{
ServiceLocator.load(new ServiceLocator(new ColonMovieFinder(”movies1.txt”)));
}
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());
}
}
Dependency Injection 和Service Locator 并不互斥,你可以同时使用它们,
一个简单的Avalon 实现版本:
public class MyMovieLister implements MovieLister, Serviceable
{
private MovieFinder finder;
public void service( ServiceManager manager )
{
finder = (MovieFinder)manager.lookup(”finder”);
}
}
service() 方法就是接口注入的例子, 它使容器可以将一个ServiceManager 对象注入
MyMovieLister 对象。
ServiceManager则是一个服务定位器。MyMovieLister 并不把ServiceManager 对象保存在字段中,
而是马上借助它找到IMovieFinder 实例,并将后者保存起来。