(转自kumaws,原帖地址:http://www.cnblogs.com/kumaws/archive/2009/04/06/from_interface_to_DependencyInjection.html)

现在在各种技术站点、书籍文章上,都能看到IoC容器、控制反转、依赖注入的字眼,而且还会有一些专门实现这些功能的开发工具等等。那么这种技术是如何演变而来的?它的适用场景是哪里?我们该不该学习并掌握这门技术?下面做出一些解释。

猫狗大战举例


我现在要做一个猫狗大战的游戏,系统内部采用了标准的OO技术,先设计了一个狗狗的抽象类。

 

public abstract class Dog

{

    public void Run();

    public void Jump();

    public void Bay();

    public abstract void Display();

}
 

假设游戏中每个狗狗跑的速度、跳的高度、叫的音量都是相同的,那么唯一不同的就是外貌了,所以 Display()是抽象的。

 

public class Poodle:Dog

{

    public override void Display()

    {

        //我是狮子狗

    }

}

public class Dachshund:Dog

{

    public override void Display()

    {

        //我是腊肠狗

    }

}

//其他狗狗.
 


了,现在我们想让游戏中加入打斗的元素,狗狗会攻击,可能你会想到只用在基类中加一个Attact()的方法,就可以这样让所有继承它的狗狗就都会攻击
了。不过问题很快就来了,你会发现AIBO(日本产的电子机械宠物狗)包括家里的绒毛玩具狗也会攻击了,这是很不合情理的事情。所以并不是所有的狗狗都会
攻击这个行为,那么有人肯定想到使用接口了,把Attact()这个方法提取到接口中,只让会攻击的狗狗实现这个接口就可以了。

 

public interface IAttact

{

    void Attact();

}

public class Poodle:Dog,IAttact

{

    public override void Display()

    {

        //我是狮子狗

    }

    

    public void Attact()

    {

        //咬一口

    }

}
 

说明为什么要针对接口编程,优点


样看起来很好,但是需求总是在变化的,现在的需求又增加了:要求每种狗狗的攻击方式不同,有的会咬,有的会扑,有的甚至会狮子吼的功夫,当然如果狗狗升级
了,还会出现更多的攻击方式。上面这个方式的缺点就显现出来了,代码会在多个子类中重复,无法知道所有狗狗的全部行为,运行时不容易改变等等。

下面这样做,我们把变化的部分提取出来,多种行为的实现用接口统一实现。

 

public class BiteAttact:IAttact

{

    public void Attact()

    {

         //咬

    }

}

public class SnapAttact:IAttact

{

    public void Attact()

    {

        //扑咬

    }

}

//其他实现
 

当我们想要增加一种行为方式时,只需继承接口就可以,并不会改变其他行为。

 

public class Poodle:Dog

{

    IAttact attact;

    public Poodle()

    {

        attact=new BiteAttact();

    }

    //这里我在调用的时候就可以动态的设定行为了

    public void SetAttactBehavior(IAttact a)

    {

        attact=a;

    }

    public void PerformAttact()

    {

        attact.Attact();

    }

    

    public override void Display()

    {

        //我是狮子狗

    }

}
 


样的设计就让狗狗的攻击行为有了弹性,我们可以动态的在运行时改变它的行为,也可以随时在不影响其他类的情况下添加更改行为。而以往的做法是行为来自
Dog基类或者继承接口并由子类自行实现,这两种方法都依赖于“实现”,我们被邦的死死的,而无法更改行为。而接口所表示的行为实现,不会被绑死在Dog
类与子类中。这就是设计模式中的一个原则:针对接口编程,不要针对实现编程。

说明为什么要“依赖抽象,不要依赖具体类”

但是,当我们使用“new”的时候,就已经在实例化一个具体类了。这就是一种实现,只要有具体类的出现,就会导致代码更缺乏弹性。就好比雕塑家做好了一个“沉思者”,想把它再改造成“维纳斯”,难度可想而知。

我们再回到猫狗大战这个游戏,为了增加趣味性,我们增加了猫狗交互的功能,如果你选择了狗狗开始游戏,那么会随机不同的场景,在固定的场景会遇到固定的猫。例如:在峭壁背景会遇到山猫,在象牙塔背景中会遇到波斯猫,在草原中会遇到云猫等。

 

Cat cat;

if(mountain){

    cat=new Catamountain();

}else if(ivory){

    cat=new PersianCat();

}else if(veldt){

    cat=new CloudCat();

}
 


是有时真正要实例化哪些类,要在运行时有一些条件决定。当一旦有变化或扩展时,就要重新打开这段代码进行修改,这就违反了我们“对修改关闭”的原则。这段
代码的依赖特别紧密,而且是高层依赖于低层(客户端依赖具体类的实现)。不过庆幸的是,我们有“依赖倒转原则”与“抽象工厂模式”来拯救我们的代码。

说明“依赖倒置”与抽象工厂模式


赖倒转原则是要依赖抽象,不要依赖具体类。也就是说客户端(高层组件)要依赖于抽象(Cat),各种猫咪(低层组件)也要依赖抽象(Cat)。虽然我们已
经创造了一个抽象Cat,但我们仍然在代码中实际地创建了具体的Cat,这个抽象并没有什么影响力,那么我们如何将这些实例化对象的代码独立出来?工厂模
式正好派上用场。工厂模式属于创建型模式,它能将对象的创建集中在一起进行处理。

相反如果你选择了猫咪角色,就会在不同的场景遇到特定的狗狗NPC。

现在我们要创建一个工厂的接口:

 

public interface Factory

{

    Cat CreateCat();

    Dog CreateDog();

}

public class MountainSceneFactory:Factory

{

    public Cat CreateCat(){

        return new Catamountain();

    }

    

    public Dog CreateDog(){

        return new CragDog();

    }

}

public class VeldtSceneFactory:Factory

{

    public Cat CreateCat(){

        return new CloudCat();

    }

    

    public Dog CreateDog(){

        return new VeldtDog();

    }

}
 

然后构建一个草原的场景:

 
public class VeldtScene : Scene  {     Factory factory;      private static Cat cat=null;
    private static Dog dog=null;     public VeldtScene(Factory f)      {         factory=f;     }      Public void prepare()     {          if(User.Identity=="Dog")              dog=factory.CreateDog();         else(user.Identity=="Cat")              cat=factory.CreateCat();     }  } 
 

这样一来,场景的条件不由代码来改变,而可以由客户端来动态改变,来看看我们的客户端吧!

Factory factory=new VeldtSceneFactory();

Scene scene=new VeldtScene(factory);

Scene.prepare();

这样如果你的角色是一只狗狗的话,就能在这个草原上见到一只云猫了。工厂模式将实例解耦出来,替换不同的工厂以取得不同的行为。

说明“将组件的配置与使用分离”

事物总是在发展,需求总是在增加。猫狗大战要升级为网络版,我们希望由开发人员开发游戏,而由技术支持人员做游戏的安装配置。一般开发者会提供配置文件交给
技术支持人员,由他们来动态的为游戏更改配置,例如在草原上出现了波斯猫,老虎却出现在客厅里。技术支持人员是无法修改源代码的,但可以让他们修改配置文
件以来改变实例的创建。


们的设计离不开一个基本原则--分离接口与实现。在面向对象程序里,我们在一个地方用条件逻辑来决定具体实例化哪一个类,以后的条件分支都由多态来实现,
而不是继续重复前面的条件逻辑。当我们决定将“选择具体实现类”的据侧推迟到部署阶段,则在装配原则上是要与应用程序的其余部分分开的,这样我就可以轻松
的针对不同的部署替换不同的配置。

简单说明依赖注入

终于可以进入本文的重点了。现在我们的目标就是要把组件的配置与使用分离开。IoC(Inversion of Control控制反转)容器因此应运而生,martin在他的大作中将此更形象的称谓依赖注入(Dependency Injection)。

依赖注入的基本思想是:用一个单独的对象获得接口的一个合适的实现,并将其实例赋给调用者的一个字段。具体的依赖注入的讲解可以看Martin Fowler的文章,不再详述。我主要以实例的形式,来更好的理解依赖注入的特点与其所带来的好处。

OO思想举例,控制翻转,依赖注入的更多相关文章

  1. Spring IOC - 控制反转(依赖注入) - 入门案例 - 获取对象的方式 - 别名标签

    1. IOC - 控制反转(依赖注入) 所谓的IOC称之为控制反转,简单来说就是将对象的创建的权利及对象的生命周期的管理过程交 由Spring框架来处理,从此在开发过程中不再需要关注对象的创建和生命周 ...

  2. 码农小汪-spring框架学习之2-spring IoC and Beans 控制反转 依赖注入 ApplicationContext BeanFactory

    spring Ioc依赖注入控制反转 事实上这个东西很好理解的,并非那么的复杂. 当某个Java对象,须要调用还有一个Java对象的时候(被依赖的对象)的方法时.曾经我们的做法是怎么做呢?主动的去创建 ...

  3. C#扫盲篇(二)依赖倒置•控制反转•依赖注入•面向接口编程--满腹经纶的说

    扫盲系列的文章收到了广大粉丝朋友的支持,十分感谢,你们的支持就是我最大动力. 我的扫盲系列还会继续输出,本人也是一线码农,有什么问题大家可以一起讨论.也可以私信或者留言您想要了解的知识点,我们一起进步 ...

  4. 玩转Spring MVC (一)---控制反转(依赖注入)

    Spring的核心是控制反转,什么是控制反转呢?小编浅述一下自己的拙见,有不当之处还希望大家指出. 控制反转(IOC),也可以叫做依赖注入(DI),这两个词其实是一个概念. 控制反转,那是什么控制被反 ...

  5. [PHP] 控制反转依赖注入的日常使用

    控制反转:控制权交给了自己的类 依赖注入:依赖另一个类,我没有手动去new它 <?php /*我自己要用的类*/ class User { private $name; private $age ...

  6. Spring框架使用(控制反转,依赖注入,面向切面AOP)

    参见:http://blog.csdn.net/fei641327936/article/details/52015121 Mybatis: 实现IOC的轻量级的一个Bean的容器 Inversion ...

  7. 控制反转&依赖注入

    IoC(Inversion of Control,控制反转).这是spring的核心,贯穿始终.所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系.这是什么 ...

  8. 控制反转 依赖注入 main函数

    通过依赖注入.服务定位实现控制反转 Go kit - Frequently asked questions https://gokit.io/faq/ Dependency Injection - W ...

  9. Spring IOC - 控制反转(依赖注入) - 单例和多例

    Spring容器管理的bean在默认情况下是单例的,即一个bean只会创建一个对象,存在map中,之后无论获取多少次该bean,都返回同一个对象. Spring默认采用单例方式,减少了对象的创建,从而 ...

随机推荐

  1. java的Spring学习3- mybatis

    1.java的maven依赖包 <properties> <project.build.sourceEncoding>UTF-8</project.build.sourc ...

  2. C#数组 动态添加元素

    string[] a = new string[] { "1", "2", "3" }; 给a追加一个 "4" 1 2 ...

  3. resty-limit-multiple-strategy.lua

    --[[ 执行过载限流策略 --]] -- 当执行限流时,Nginx 返回的状态码 err_code = local limit_config = { user_limit = {rate = , b ...

  4. oracle 基础知识(十)----exp/imp--->>>>>expdp/impdp

    一,简介 存活下来的远古级别的导入导出软件exp/imp  ,软件多数使用于oracle 9i 之前 到了10g以后基本全面被数据库泵(Data Pump)取代,即expdp/impdp.本文会分别介 ...

  5. 阿冰教你一步一步做Android新闻客户端(二)两种异步线程加载图片的方法

    哈哈哈抱着没人看的心态随便写,直接上代码,各位看官看注释 一种Thread  一种AsyncTask 先不说用框架 public class ImageLoader { private ImageVi ...

  6. IDEA 导入cordova3.5工程目录注意事项

    IDEA 导入cordova3.5工程目录注意事项 1 eclipse很不稳定,有很多小问题.平时我自己用idea,但是当用cordova3.5创建好工程目录是,用eclipse导入时没有问题的.但是 ...

  7. TOJ 3660 家庭关系

    描述 给定若干家庭成员之间的关系,判断2个人是否属于同一家庭,即2个人之间均可以通过这些关系直接或者间接联系. 输入 输入数据有多组,每组数据的第一行为一个正整数n(1<=n<=100), ...

  8. 【密码学】RSA算法原理

    RSA算法是一种非对称密码算法,所谓非对称,就是指该算法需要一对密钥,使用其中一个加密,则需要用另一个才能解密. RSA的算法涉及三个参数,n.e1.e2. 其中,n是两个大质数p.q的积,n的二进制 ...

  9. linus系统下使用hexo搭建个人博客

    最近在搭建自己的个人网站,准备在上面内置一个博客模块,把之前或者以后杂七杂八的总结都放里边. 大致查了一下在WordPress和Hexo间选用了Hexo,体量较小一点. 先贴上Hexo的官方文档:He ...

  10. 九度oj题目1385:重建二叉树

    题目1385:重建二叉树 时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:4419 解决:1311 题目描述: 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树.假设输入的前序遍历和 ...