`
itwangxinli
  • 浏览: 147908 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

IOC设计模式简介

阅读更多

Inversion of Control Containers and the Dependency Injection pattern

In the Java community there's been a rush of lightweight containers that help to assemble components from different projects into a cohesive application. Underlying these containers is a common pattern to how they perform the wiring, a concept they refer under the very generic name of "Inversion of Control". In this article I dig into how this pattern works, under the more specific name of "Dependency Injection", and contrast it with the Service Locator alternative. The choice between them is less important than the principle of separating configuration from use.

Last significant update: 23 Jan 04

| Chinese (中文PDF下载)|guese | French | Italian |


One of the entertaining things about the enterprise Java world is the huge amount of activity in building alternatives to the mainstream J2EE technologies, much of it happening in open source. A lot of this is a reaction to the heavyweight complexity in the mainstream J2EE world, but much of it is also exploring alternatives and coming up with creative ideas. A common issue to deal with is how to wire together different elements: how do you fit together this web controller architecture with that database interface backing when they were built by different teams with little knowledge of each other.A number of frameworks have taken a stab at this problem, and several are branching out to provide a general capability to assemble components from different layers. These are often referred to as lightweight containers, examples include PicoContainer, and Spring.

Underlying these containers are a number of interesting design principles, things that go beyond both these specific containers and indeed the Java platform. Here I want to start exploring some of these principles. The examples I use are in Java, but like most of my writing the principles are equally applicable to other OO environments, particularly .NET.


Components and Services

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

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

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

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


A Naive Example

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

In this example I'm writing a component that provides a list of movies directed by a particular director. This stunningly useful function 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, it asks a finder object (which we'll get to in a moment) to return every film it knows about. Then it just hunts through this list to return those directed by a particular director. This particular piece of naivety I'm not going to fix, since it's just the scaffolding for the real point of this article.

The real point of this article is this finder object, or particularly how we connect the lister object with a particular finder object. The reason why this is interesting is that I want my wonderful moviesDirectedBy method to be completely independent of how all the movies are being stored. So all the method does is refer to a finder, and all that finder does is know how to respond to the findAll method. I can bring this out by defining an interface for the finder.

public interface MovieFinder {
List findAll();
}

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

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

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

Now if I'm using this class for just myself, this is all fine and dandy. But what happens when my friends are overwhelmed by a desire for this wonderful functionality and would like a copy of my program? If they also store their movie listings in a colon delimited text file called "movies1.txt" then everything is wonderful. If they have a different name for their movies file, then it's easy to put the name of the file in a properties file. But what if they have a completely different form of storing their movie listing: a SQL database, an XML file, a web service, or just another format of text file? In this case we need a different class to grab that data. Now because I've defined a MovieFinder interface, this won't alter my moviesDirectedBy method. But I still need to have some way to get an instance of the right finder implementation into place.

Figure 1 shows the dependencies for this situation. The MovieLister class is dependent on both the MovieFinder interface and upon the implementation. We would prefer it if it were only dependent on the interface, but then how do we make an instance to work with?

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

Expanding this into a real system, we might have dozens of such services and components. In each case we can abstract our use of these components by talking to them through an interface (and using an adapter 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 use plugins to handle the interaction with these services so we can use different implementations in different deployments.

So the core problem is how do we assemble these plugins into an application? This is one of the main problems that this new breed of lightweight containers face, and universally they all do it using 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.


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

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.

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

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 its developers do prefer constructor injection.

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

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

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

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 wit

分享到:
评论

相关推荐

    IoC小例子(了解一下IoC设计模式入门)

    IoC设计模式的实现通常依赖于容器,如Spring框架在Java中就是一个著名的IoC容器。开发者可以通过XML配置、注解或者编程式的方式来定义对象及其依赖关系。例如,在Spring中,我们可以在XML配置文件中定义Bean(代表一...

    Android应用开发中控制反转IoC设计模式使用教程

    【Android应用开发中控制反转IoC设计模式使用教程】 IoC(Inversion of Control,控制反转)是一种设计模式,常被称为依赖注入(Dependency Injection,DI)。在Android应用开发中,IoC模式能够显著降低组件之间的...

    JAVA设计模式之IOC实战02

    JAVA设计模式之IOC实战02

    JAVA设计模式之IOC实战01

    JAVA设计模式之IOC实战01

    IOC模式 c#经典例子

    IOC(Inversion of Control,控制反转)模式是一种软件设计原则,它在面向对象编程中用于解耦组件之间的依赖关系。C#中实现IOC的一种常见方式是通过依赖注入(Dependency Injection,DI)。在这个“IOC模式 c#经典...

    Spring IOC设计原理解析.docx

    IOC,即控制反转,是软件设计模式中的一种,它将对象的创建和管理权交给了框架,而不是由对象自身负责。DI(Dependency Injection,依赖注入)是IOC的一种具体实现方式,它允许开发者在不修改代码的情况下改变对象间...

    Java Spring代理模式AOP-IOC分析

    IOC是一种软件设计模式,旨在将对象的创建和管理从应用程序中分离出来。在Spring框架中,IOC容器负责创建和管理对象,并提供了一种机制来将对象注入到应用程序中。 六、Spring实现AOP 在Spring框架中,可以使用...

    SpringIOC和AOP原理设计模式

    **Spring IOC原理与设计模式** 控制反转(IOC)是一种设计思想,它改变了传统应用程序中对象之间的依赖关系管理方式。在没有IOC的情况下,对象通常自行创建其依赖的对象实例,而在Spring中,这个过程由Spring的IOC...

    Android 设计模式 示例集合 以及IOC注解事例 Demo

    这个压缩包"Android 设计模式 示例集合 以及IOC注解事例 Demo"显然包含了多种设计模式的实践示例和IOC(Inversion of Control)注解的应用。下面将详细探讨这些关键知识点。 1. **设计模式**: - **单例模式**:...

    工厂模式的IoC应用

    工厂模式则是一种常用的对象创建型设计模式,它提供了一种创建对象的最佳方式。当Spring框架结合工厂模式时,可以实现更灵活、可扩展的系统架构。 **Spring框架中的BeanFactory** 在Spring框架中,`BeanFactory`是...

    IoC容器的设计(利用反射、注解和工厂模式实现)

    1. 利用注解、反射和工厂模式设计一个简单的IoC容器 2. 该IoC容器包含3个注解和一个IoC容器类(AnnotationConfigApplicationContext),其定义如下: 注解 含义 @Component 标注Bean @Autowired 标注需要被注入的...

    Java使用IOC控制反转的三种设计模式详解

    Java 使用 IOC 控制反转的三种设计模式详解 控制反转(IoC)是一种软件设计模式,目的是为了降低程序模块之间的耦合度,使得系统更加灵活和可维护。 Java 是一种广泛使用的编程语言,对于 Java 开发人员来说,掌握 ...

    IoC 容器和Dependency Injection 模式

    依赖注入(DI)是一种设计模式,它实现了 IoC 的思想。在 DI 中,组件不再直接创建其所依赖的对象,而是将这些依赖作为参数传递进来。这种方式有多种实现形式: 1. **构造函数注入**(Constructor Injection):...

    Java的反射与代理实现IOC模式

    ### Java的反射与代理实现IOC模式 #### 一、Java反射机制概述与初探 Java反射机制是Java语言的一项重要特性,它使得程序能够在运行时动态地获取对象的信息并进行操作。这种强大的特性使得Java更加灵活多变,尤其是...

    Spring中IoC优点与缺点解析

    IoC(Inversion of Control)是 Spring 框架中的一种设计模式,它的主要思想是将对象的创建和管理交给容器,从而解耦合对象之间的依赖关系。今天,我们将详细解析 IoC 的优点和缺点。 优点 1. 简化对象的创建:IoC ...

    李建忠 C#面向对象设计模式纵横谈(25):设计模式总结

    在IT行业中,面向对象设计模式是软件开发中的重要概念,尤其在C#这样的面向对象编程语言中更是不可或缺。李建忠先生的“C#面向对象设计模式纵横谈”系列讲座,通过对不同设计模式的深入剖析,帮助开发者更好地理解和...

    学习Spring笔记_IoC(控制反转)简介

    在软件开发中,IoC(Inversion of Control,控制反转)是一种设计模式,它将对象的创建和管理责任从代码中剥离出来,交由一个容器来处理。Spring框架是Java平台上的一个核心组件,其核心特性就是依赖注入...

    利用Java的反射与代理实现IOC模式

    IOC模式是一种设计模式,它将对象的创建和依赖关系的管理从代码中分离出来,使系统更加灵活、可维护和可测试。在这个主题中,我们将深入探讨Java反射和动态代理如何帮助我们实现IOC。 首先,让我们理解Java反射。...

Global site tag (gtag.js) - Google Analytics