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

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

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

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

二、什么叫做IoC(Inversion of Control)控制反转
这一段是纯理论的介绍,从别的地方直接抄过来的,先看一遍,不懂也没关系,下面我会举例来说明:
[quote]
什么是Inversion of Control
控制反转。在Java开发中,IoC意味着将你设计好的类交给系统去控制,而不是在你的类内部控制。这称为控制反转。

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


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

[size=medium][color=blue]1、首先我们传统的编程方式是这样的:[/color][/size]
先建立一个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个黑色的机器人去给一楼的邻居打招呼;白色的去二楼;红色的去三楼等等。如果某天发现红色的油漆没了需要换成蓝色,那岂不是得翻出每个红色机器人的实现类,然后一个个的去改掉这个机器人实例的颜色? 太累了!


[size=medium][color=blue]2、使用简单工厂和静态工厂模式来创建机器人:[/color][/size]
用简单工厂模式来创建,那就相对简单方便一些了,我们会在工厂类里事先写好一些常用类型的机器人,然后只要用机器人的编号来获得相应的机器人就行了。
还是调用那个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!");
}
}

以上就是静态工厂的实现方式了!
[color=red][b]注意!这里有两个细节:[/b][/color]
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、[color=gray]抽象工厂模式(误)[/color][size=medium][color=blue]多亏yelinsen05的指正,我再查了一下相关资料,这第三种方式其实还是简单工厂模式,误人子弟了...sorry![/color][/size]关于真正的抽象工厂模式,我下回再介绍。学艺不精,献丑了....
真正的抽象工厂介绍请移步看我另外一篇博文:[url]http://bolide74.iteye.com/blog/1001900[/url]


现在为了解决简单工厂和静态工厂模式的缺陷,我们可以把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!");
}
}

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

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

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

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

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

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

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


下一篇:Spring温故知新(三)singleton单例模式 [url]http://bolide74.iteye.com/blog/1001630[/url]
上一篇:Spring温故知新(一)Hello,World! [url]http://bolide74.iteye.com/blog/993248[/url]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值