`
bolide74
  • 浏览: 83299 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Spring温故知新(二) IoC控制反转与DI依赖注入(修正1)

阅读更多
    学习Spring,所有教程都是从IoC和DI开始的,但是大部分教程对它们的介绍都很抽象,要是之前没系统的学习过设计模式的话理解起来就非常吃力。所以在这里我尽我的能力来解释这两个概念。

首先的重点,IoC并不是Spring特有的,它是一种设计模式,事实上只要掌握了原理,自己也可以写出一个IoC的实现方法。

一、DI(Dependency Injection)依赖注入可以称为IoC(Inversion of Control)控制反转,但是IoC不等于就是DI
    这一点是很多初学者很容易产生错误的一个概念,因为很多教程都把DI和IoC放在一起说,就像堆和堆栈一样,容易让初学者以为他们就是同一个东西,这个是需要纠正的。
    简单的解释,就是IoC包括了DI,但是IoC还包括了另外一个叫做DL(Dependency Lookup)依赖查找的功能。你可以先简单的把IoC当作是某种操作,DI是这种操作的写入方式,而DL是查找方式,这样可能容易区分一点。
    DL之所以很少有人提起,是因为我们平时很少用到,所以也就慢慢被遗忘了,但是这也不代表它就不存在了。可惜我自己也对DL没有去花时间了解,只是知道有这么个东西,所以在这暂时也没法去详细介绍了,如果有高手的话请指点一下!

二、什么叫做IoC(Inversion of Control)控制反转
    这一段是纯理论的介绍,从别的地方直接抄过来的,先看一遍,不懂也没关系,下面我会举例来说明:
引用

什么是Inversion of Control
        控制反转。在Java开发中,IoC意味着将你设计好的类交给系统去控制,而不是在你的类内部控制。这称为控制反转。

IoC的原理
        不创建对象,但是描述创建它们的方式。在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务。容器负责将这些联系在一起。其原理是基于OO设计原则的The Holly wood Principle:Don't call us, we'll call you(别找我,我会来找你的)。也就是说,所有的组件都是被动的(Passive),所有的组件初始化和调用都由容器负责。组件处在一个容器当中,由容器负责管理。简单的来讲,就是由容器控制程序之间的关系,而非传统实现中,由程序代码直接操控。这也就是所谓“控制反转”的概念所在:控制权由应用代码中转到了外部容器,控制权的转移,这就是反转。

     如果你只需要看这段文字介绍就能理解什么叫IoC了,那下面的那些也就不用看了,大侠!
     如果你也跟我一样属于笨的那种,那么可以看一下我下面的介绍:


我们沿用之前的HelloWorld的例子来介绍IoC。
假设我要跟我们家的邻居去打招呼,但是我又懒得出门,所以我干脆造了一个机器人代替我去打招呼了(这得要多宅的一个人啊! 宁可造个机器人也不想出个门)。

1、首先我们传统的编程方式是这样的:
先建立一个Robot类,然后类里面实现一个喊话的方法,然后实现方法里创建一个机器人的实例来调用喊话:
Robot类:
package com.iteye.bolide74.action;

public class Robot {
	public String name;
	public String color;
	public double height;
	public double width;
	public double weight;

	public Robot(String name, String color, double height, double width,
			double weight) {
		this.name = name;
		this.color = color;
		this.height = height;
		this.width = width;
		this.weight = weight;
	}

	public void Speak(String msg) {
		System.out.println(msg);
	}
}

实现类:
package com.iteye.bolide74.tester;

import com.iteye.bolide74.action.Robot;

public class IoCTester {
	public static void main(String[] args) {
		Robot robot0 = new Robot("robot0", "black", 80.000, 40.0000, 1000.0000);
		robot0.Speak("Hello,World!");
	}
}


优点:方便简单啊!随便学个一天JAVA就会用啊!
缺点:写代码倒是方便了,但是以后维护起来就超级麻烦。既然是机器人那么制造的时候势必还会涉及它的名字、颜色、高矮胖瘦之类的参数。假设我要造N个黑色的机器人去给一楼的邻居打招呼;白色的去二楼;红色的去三楼等等。如果某天发现红色的油漆没了需要换成蓝色,那岂不是得翻出每个红色机器人的实现类,然后一个个的去改掉这个机器人实例的颜色? 太累了!


2、使用简单工厂和静态工厂模式来创建机器人:
用简单工厂模式来创建,那就相对简单方便一些了,我们会在工厂类里事先写好一些常用类型的机器人,然后只要用机器人的编号来获得相应的机器人就行了。
还是调用那个Robot类不变,只改实现类:
package com.iteye.bolide74.tester;

import com.iteye.bolide74.action.Robot;

class RobotFactory {
	public Robot getRobot(int robotType) {
                Robot robot;
		switch (robotType) {
		case 0:
			robot = new Robot("robot0", "black", 80.000, 40.0000, 1000.0000);
			break;
		case 1:
			robot = new Robot("robot1", "white", 90.000, 30.0000, 800.0000);
			break;
		default:
			robot = new Robot("defaultRobot", "red", 10.000, 10.0000, 300.0000);
			break;
		}
		return robot;
	}
}

public class IoCTester {
	public static void main(String[] args) {
		RobotFactory robotFactory = new RobotFactory();
		Robot robot = robotFactory.getRobot(0);
		robot.Speak("Hello,World! My name is " + robot.name);
	}
}

这样的话后期维护就方便很多了,当有一天需要把已经造好的所有红色机器人换成蓝色,那就只要在getRobot方法里相应的new Robot()里的red参数换成blue就行了。简单么?
但是我觉得为了这么一个单一的类多写一个工厂太麻烦了,有没有办法省略这个工厂呢?答案是肯定的,我们可以直接把getRobot这个获取实例的方法写在Robot类里面就不需要新建专门的工厂类了,这里新建一个Robot2类来与原先的Robot类区分开:
package com.iteye.bolide74.action;

public class Robot2 {
	public String name;
	public String color;
	public double height;
	public double width;
	public double weight;

	private Robot2(String name, String color, double height, double width,
			double weight) {
		this.name = name;
		this.color = color;
		this.height = height;
		this.width = width;
		this.weight = weight;
	}

	public static Robot2 getRobot(int robotType) {
		Robot2 robot2;
		switch (robotType) {
		case 0:
			robot2 = new Robot2("robot0", "black", 80.000, 40.0000, 1000.0000);
			break;
		case 1:
			robot2 = new Robot2("robot1", "white", 90.000, 30.0000, 800.0000);
			break;
		default:
			robot2 = new Robot2("defaultRobot", "red", 10.000, 10.0000, 300.0000);
			break;
		}
		return robot2;
	}
	public void Speak(String msg) {
		System.out.println(msg + ",我是" + this.name);
	}
}

实现类
package com.iteye.bolide74.tester;

import com.iteye.bolide74.action.Robot2;

public class IoCTester {
	public static void main(String[] args) {
		Robot2 robot2 = Robot2.getRobot(1);
		robot2.Speak("Hello,world!");
	}
}

以上就是静态工厂的实现方式了!
注意!这里有两个细节:
1) Robot2的构造方法是private的,这样的好处就是所有Robot2的使用者或者说是实现类在获取机器人的时候,只能通过getRobot2来获取,这样就实现了规范化,而不是有些地方是直接new的,有些地方是用getRobot方法来的(一个项目,不一定就是一个程序员写到老的...)。
2) Robot2获取实例的getRobot方法是static静态的(所以才叫静态工厂嘛~)!由于Robot2的构造函数是private的了,不能直接new一个实例,那就更加不可能调用需要实例化才能使用的getRobot方法了,所以必须要设置为static方法,就可以无需实例化直接调用!
      现在用了以上两种工厂方法,只要在需要机器人的时候在getRobot(int robotType)里设进想要的机器人的型号,机器人工厂就会自动送过来我想要的机器人了!

      不过现在又出现了一种状况,就是我的GF正好在家,那么我就不用这么麻烦的造机器人了,我只要叫我女朋友去招呼就行了。(TNND就这种宅男+使唤癖,能有GF就有鬼了!)
      那么再用这个简单工厂和静态工厂模式就不适用了,毕竟GF不是机器人(我看以这人的脾气其实很有可能GF也是机器人...),那么就得再写一个People类,然后再在这个类里实现Speak方法,然后再新建一个Girlfriend实例来实现speak方法打招呼。你也觉得这样太累了吧?毕竟People类和Robot类都是用来Speak而已,要是来回切换不同的类去Speak的话就得重复建立不同的实例和不同的Speak方法,还是很麻烦!


3、抽象工厂模式(误)多亏yelinsen05的指正,我再查了一下相关资料,这第三种方式其实还是简单工厂模式,误人子弟了...sorry!关于真正的抽象工厂模式,我下回再介绍。学艺不精,献丑了....
真正的抽象工厂介绍请移步看我另外一篇博文:http://bolide74.iteye.com/blog/1001900


现在为了解决简单工厂和静态工厂模式的缺陷,我们可以把Speak这个方法(或者可以称为功能模块)抽取出来,作为一个接口来实现,这样只要People类和Robot类都实现这个接口,就能够通用了。
package com.iteye.bolide74.impl;

public interface ISpeaker {
	public void Speak(String msg);
}

接下来是新的Robot类和People类,它们都同时实现了ISpeaker这个接口,两者都用了不同的Speak内容来区分人类和机器人说话方式的不同:
package com.iteye.bolide74.action;

import com.iteye.bolide74.impl.ISpeaker;

public class Robot implements ISpeaker {
	public String name;
	public String color;
	public double height;
	public double width;
	public double weight;

	public Robot(String name, String color, double height, double width,
			double weight) {
		this.name = name;
		this.color = color;
		this.height = height;
		this.width = width;
		this.weight = weight;
	}

	@Override
	public void Speak(String msg) {
		System.out.println(msg + ",我是" + this.name + ",我的体重为" + this.weight);
	}
}
package com.iteye.bolide74.action;

import com.iteye.bolide74.impl.ISpeaker;

public class People implements ISpeaker {
	public String name;
	public double height;
	public double weight;

	public People(String name, double height, double weight) {
		this.name = name;
		this.height = height;
		this.weight = weight;
	}

	@Override
	public void Speak(String msg) {
		System.out.println(msg + ",我是" + this.name + ",我就不告诉你我有多重!");
	}
}

接下来就是工厂类和实现类了。这个工厂类就不是生产具体的实例对象了,而是生产一个通用的能够Speak的抽象实例(某个能打招呼的东西,或许是人类,或许是机器人)
package com.iteye.bolide74.tester;

import com.iteye.bolide74.action.People;
import com.iteye.bolide74.action.Robot;
import com.iteye.bolide74.impl.ISpeaker;

class SpeakerFactory {
	public ISpeaker getSpeaker(int speakerType) {
		ISpeaker speaker;
		switch (speakerType) {
		case 0:
			speaker = new Robot("robot0", "black", 80.000, 40.0000, 1000.0000);
			break;
		case 1:
			speaker = new People("GirlFriend", 1.64, 99.99);
			break;
		default:
			speaker = new Robot("defaultRobot", "red", 10.000, 10.0000,
					300.0000);
			break;
		}
		return speaker;
	}
}

public class IoCTester {
	public static void main(String[] args) {
		SpeakerFactory speakerFactory = new SpeakerFactory();
		ISpeaker speaker = speakerFactory.getSpeaker(1);
		speaker.Speak("Hello,World!");
	}
}

输出结果:
引用
Hello,World!,我是GirlFriend,我就不告诉你我有多重!

如此一来的话就很方便了,我们不用再像之前的简单工厂模式那样每次打招呼的时候都要重新实例化一个不同的工厂,而是实例化了一个能生成某种类别不确定但是一定能够speak打招呼的对象的通用工厂,然后用统一ISpeaker接口来实例化“打招呼”这个功能模块,至于打招呼是让机器人去还是让女朋友去就只要抽象工厂生成的时候告诉它一下代号就行了。
这样的话就能够在尽可能少修改实现代码的前提下,尽可能的实现各种情况下的同一种功能。

阶段性小结:
你可能会有点奇怪为什么我还没说到IoC这个重点,就开始阶段性小结了。
如果我说我其实已经告诉你IoC的原理了,你会惊讶吗?

我们现在通过前面几种工厂模式的方法,再来回顾一下IoC的原理理论:
1)不创建对象,但是描述创建它们的方式
      在抽象工厂模式的实现类里,我们并没有创建对象,而是直接获取了一个SpeakerFactory工厂已经生成好了的实例对象,而我们只是向SpeakerFactory工厂的生产实例对象方法描述了具体要创建怎么样的一个实例对象,也就是speakerType参数。

2)所有的组件都是被动的(Passive),所有的组件初始化和调用都由容器负责。组件处在一个容器当中,由容器负责管理。
       假设SpeakerFactory工厂是一个“容器”、robot类和people类都是“组件”,让我们看看它是不是符合上面的要求。
       所有的robot类和people类,或者说是Speaker模块的action类,它们的初始化和调用,是不是由SpeakerFactory工厂来负责的?它们是不是都处在SpeakerFactory工厂当中,由SpeakerFactory工厂负责管理的?

3)这也就是所谓“控制反转”的概念所在:控制权由应用代码中转到了外部容器,控制权的转移,这就是反转。
       现在我们可以回顾一下第一种传统方法和下面两种工厂方法的区别了。
       在传统方法中,生成实例对象的控制权是在实现类也就是应用代码中的;而在工厂模式里,控制器就明显被转移了:生成实例对象的控制权已经被工厂类掌握,而应用代码里只负责告诉工厂需要什么样的实例对象,然后就能拿到工厂方法自动送上的生成好的想要的实例对象了!

因此根据我的理解,所谓的IoC其实就是工厂模式的某一个变种,只不过Spring框架把它再发扬光大了,把Factory用泛型再次封装以后真正的容器化,把所有的组件模块的初始化和调用都抽离出来放在了一个XML配置文件里统一管理,然后利用了反射机制来获取实际的实例对象。

       当然以上都是我个人的理解,如有疑惑请翻查对比其他资料
       我的围脖:http://t.qq.com/bolide74


下一篇:Spring温故知新(三)singleton单例模式 http://bolide74.iteye.com/blog/1001630
上一篇:Spring温故知新(一)Hello,World! http://bolide74.iteye.com/blog/993248






31
8
分享到:
评论
16 楼 yintingbird 2011-05-04  
wellbbs 写道
好文章,初学者很容易看懂。

15 楼 C.T 2011-04-28  
IOC 控制反转,我觉得说明为 依赖倒置 更适合,通过将写好的类交给FW处理,让我们在编码过程中,无论是逻辑的实现,还是真的实现类都依赖于接口,编码的时候,针对接口变成,而不是针对实现类编程 ,让本来依赖于实现类的编程方法,转化为依赖于接口的实现方式。
  个人愚见,欢迎拍砖。
14 楼 wellbbs 2011-04-20  
好文章,初学者很容易看懂。
13 楼 heavensay 2011-04-15  
  我的理解是 根据配置文件+约定,然后容器包办一切事务,包括类的创建、依赖类的注入、类方法的调用。
   自己只要实现了这个类,并把业务逻辑实现好,所以其他的就可以等容器帮你搞定
12 楼 bolide74 2011-04-14  
masterHU 写道
楼主辛苦了。这博文对我进一步理解Spring的原理更加透彻了。
不过你说到的依赖查找是不是JNDI? 我怎么记得我看过的资料是DI和JNDI呢?
求指教。

很抱歉的告诉你,另外一种我也不清楚,只是我查到的是DL,但是不是JNDI就不清楚了,总之我只是给了一个提醒,不要直接把IoC和DI划等号,另外的东西是什么,那就该自己慢慢琢磨了。只不过这个暂时和Spring无关,所以我没有去深究。
11 楼 masterHU 2011-04-14  
楼主辛苦了。这博文对我进一步理解Spring的原理更加透彻了。
不过你说到的依赖查找是不是JNDI? 我怎么记得我看过的资料是DI和JNDI呢?
求指教。
10 楼 bolide74 2011-04-13  
yelinsen05 写道
第三个并不是抽象工厂模式啊!是简单工厂设计模式!需要学习的话推荐Head First 设计模式!

我查了一下相关资料,你说的确实没错,第三个应该还是一个简单工厂模式。抽象工厂模式视乎还要再封装一层抽象类的工厂,具体的我抽空再研究一下!
谢谢您的指正!
9 楼 yelinsen05 2011-04-13  
第三个并不是抽象工厂模式啊!是简单工厂设计模式!需要学习的话推荐Head First 设计模式!
8 楼 竹隐江南 2011-04-13  
期待续集啊。。。
7 楼 纪红玉 2011-04-13  
ItEye 10号群 现招人 QQ 群号:68123515
女程序员群7736086
6 楼 tfwin2 2011-04-13  
对我理解设计模式有很大好处,由浅入深,彪悍的文章不需要解释!
5 楼 凤凰山 2011-04-12  
期待持续。。。。。。。。。
4 楼 a657439380 2011-04-12  
有机会得恶补下设计模式了,现在只是停留在能用而不知道原理的层面上。。。。。
3 楼 twincle 2011-04-12  
bolide74 写道
twincle 写道
好文章,不是简单的从代码实践的层面描述spring,而是从实现原理的层面上描述,而且还对比了多种实现方式

总算是有人响应了一下...  多谢支持!  这一篇博文花了我接近10个小时的时间,发现写博文还真是不容易。不过有您的支持这一切都值得了!

哈哈 不客气啦 这种原创文章好难得的 至少市面上卖的那些教程书都不会说这么详细生动
2 楼 bolide74 2011-04-12  
twincle 写道
好文章,不是简单的从代码实践的层面描述spring,而是从实现原理的层面上描述,而且还对比了多种实现方式

总算是有人响应了一下...  多谢支持!  这一篇博文花了我接近10个小时的时间,发现写博文还真是不容易。不过有您的支持这一切都值得了!
1 楼 twincle 2011-04-12  
好文章,不是简单的从代码实践的层面描述spring,而是从实现原理的层面上描述,而且还对比了多种实现方式

相关推荐

Global site tag (gtag.js) - Google Analytics