`
msi110
  • 浏览: 31614 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

看《墨攻》理解IoC

阅读更多

     IoC(反向控制:Inverse of Control)是Spring容器的底层核心功能,AOP功能、声明事务等功能在此基础上生根开花。但是IoC这个重要的概念却比较晦涩隐讳,拐弯抹角,不容易让人望文生义,不能不说是一大遗憾。不过IoC确实包括很多内涵,它涉及到代码解耦,设计模式优化等问题的考量。
      2006年多部贺岁大片以让人应接不暇的频率纷至沓来,其中张之亮的《墨攻》算是比较出彩的一部,讲述了战国时期墨家人革离帮助梁国反抗赵国侵略的个人英雄主义故事,恢宏壮阔,浑雄凝重的历史场面相当震撼。其中有一个场景:当刘德华所饰的墨者革离到达梁国都城下,城上梁国守军问:“来者何人?”,刘德华回答:“墨者革离!”,我们不妨用Java对这段“城门问对”的场景进行编剧并借由这个例子来理解IoC的内涵。
     剧本和饰演者耦合
     MoAttack代表《墨攻》的剧本,cityGetAsk()代表“城门问对”这段剧情,LiuDeHua是具体饰演者刘天王:

/**
 * 代码清单 1  MoAttack:通过演员安排剧本
 */
public class MoAttack {
	public void cityGateAsk() {
		LiuDeHua ldh = new LiuDeHua(); // ① 演员直接侵入剧本
		ldh.responseAsk("墨者革离!");
	}
} 

     我们会发现以上剧本在①处,作为具体饰演者的刘德华直接侵入到剧本中,使剧本和演员直接耦合在一起:

 

图 1 剧本和演员直接耦合


      一个明智的编剧在剧情创作时应围绕故事的角色进行,而不应考虑角色的具体饰演者,这样才可能在剧本投拍时自由地遴选任何适合的演员,而非绑定在刘德华一人身上。通过以上的分析,我们知道需要为该剧本主人公革离定义一个接口,以角色进行剧情安排,饰演者实现角色的接口:

/**
 * 代码清单 2 MoAttack:引入剧本角色
 */
public class MoAttack{
    public void cityGateAsk(){
        GeLi geli = new LiuDeHua();//① 引入革离角色接口
        geli.responseAsk("墨者革离!");//② 通过接口开展剧情
   }
}

 

 

    在①处引入了剧本的角色——革离,剧本的情节通过角色展开,在拍摄时角色的事迹由演员表现,如②处所示。因此墨攻、革离、刘德华三者的类图关系如图 2所示:

 

 

      我们希望剧本和演员无关,可是,在图2中,我们看到MoAttack同时依赖于GeLi接口和LiuDeHua类,并没有达到我们所期望的剧本仅依赖于角色的目的。可是角色最终又必须通过具体的演员才能完成拍摄,如何将让LiuDeHua和剧本无关而又能完成GeLi的具体动作呢?当然是在影片投拍时,导演将LiuDeHua安排在GeLi的角色上,通过导演之手将剧本、角色、饰演者装配起来。


图 3 剧本和饰演者解耦了


      通过引入导演,剧本和具体的饰演者解耦了,对应到软件中,导演象是一个装配器,将具体的饰演者赋给了剧本的角色。
现在我们可以反过来讲解IoC的概念了。IoC(Inverse of Control)的字面意思是控制反转,它包括两个层面的内容:其一是“控制”,其二是“反转”,到底是什么东西的控制被反转了呢?对应到前面的例子, “控制”是指GeLi角色扮演者的选择控制权,“反转”是指这种选择控制权从《墨攻》剧本中移除,转交到导演的手中。对于程序来说,即是某一接口具体实现类的选择控制权从客户类中移除,转交给第三方来确定,客户类不知道是哪个具体的实现类,它通过接口方法对实现类进行调用。
      因为IoC确实不够开门见山,因此业界曾进行了广泛的讨论,最终软件界的泰斗级人物Martin Fowler提出了DI(依赖注入:Dependency Injection)的概念,即将客户类对接口实现类的依赖关系由第三方(容器或协作类)注入,以移除客户类对具体接口实现类的依赖。“依赖注入”的概念显然比“控制反转”直接达意,易于理解。

IoC的三种类型

      从注入方法上看,主要可以划分为三种的注入类型,分别是构造函数注入、属性注入和接口注入,Spring支持构造函数注入和属性注入。下面我们继续使用以上的例子说明这三种注入方法的区别。

1、构造函数注入

    我们通过客户类的构造函数,将接口实现类通过接口变量传入,如代码清单 3 3所示:

public class MoAttack{
    private GeLi geli;
    public MoAttack(GeLi geli){//① 注入革离的具体扮演者
        this.geli = geli;
    }
    public void cityGateAsk(){
        geli.responseAsk("墨者革离!");
    }
}

 代码清单 3 MoAttack:通过构造函数注入革离扮演者

 

    MoAttack的构造函数不关心具体是谁扮演革离这个角色,只要在①处传入的扮演者按剧本要求完成角色功能即可。
    角色的具体扮演者由导演来安排,如代码清单4所示:

public class Director{
    public void direct(){
        GeLi geli = new LiuDeHua();//① 指定角色的扮演者
        MoAttack moAttack = new MoAttack(geli);//② 注入具体扮演者到剧本中
        moAttack.cityGateAsk();
    }
}

  代码清单 4 Director:通过构造函数注入革离扮演者

     在①处,导演安排刘德华饰演革离的角色,并在②处,将刘德华“注入”到墨攻的剧本中,然后开始“城门问答”剧段的演出工作。

2、属性注入

      有时,导演会发现,虽然革离是影片《墨攻》的第一主人公,但并非每场戏都需要革离的出现,通过构造函数方式注入显得很不妥当,在这种情况下,可以使用属性注入进行改造。属性注入通过setter方法完成客户类所需依赖的注入,更灵活,更方便。

public class MoAttack {
    private GeLi geli;
    public void setGeli(GeLi geli){//① 属性注入方法
        this.geli = geli;
    }
    public void cityGateAsk(){
        geli.responseAsk("墨者革离");
    }
}

  代码清单 5 MoAttack:通过setter方法注入革离扮演者

      MoAttack在①处为geli属性提供一个setter方法,以便让导演在拍需要革离的戏时才将注入geli的具体扮演者,而不需要刘德华从头到尾跟着墨攻剧组跑。

public class Director {
    public void direct(){
        GeLi geli = new LiuDeHua();
        MoAttack moAttack = new MoAttack();
        moAttack.setGeli(geli); //① 调用属性setter方法注入
        moAttack.cityGateAsk();
    }
}

代码清单 6 Director:通过setter方法注入革离扮演者


      和通过构造函数注入革离扮演者不同,在实例化MoAttack时,并未指定任何扮演者,而是在实例化MoAttack后,调用其setGeli()方法注入扮演者。按照类似的方式,我们还可以为剧本中其他如巷淹中,梁王等角色分别提供注入的setter方法,导演即可以根据所拍剧段的不同,注入所需要的角色了。

3、接口注入

     将客户类所有注入的方法抽取到一个接口中,客户类通过实现这一接口提供注入的方法。为了采取接口注入的方式,需要声明一个额外的接口:

public interface ActorArrangable {
    void injectGeli(GeLi geli);
}

      然后,MoAttack实现这个接口并实现接口中的方法:

public class MoAttack implements ActorArrangable {
    private GeLi geli;

    public void injectGeli(GeLi geli) { // ① 实现接口方法
        this.geli = geli;
    }

    public void cityGateAsk() {
        geli.responseAsk("墨者革离");
    }
}

 代码清单 7 MoAttack:通过接口方法注入革离扮演者


    Director通过ActorArrangable的injectGeli()方法完成扮演者的注入工作。

public class Director {
    public void direct() {
        GeLi geli = new LiuDeHua();
        MoAttack moAttack = new MoAttack();
        moAttack.injectGeli(geli);
        moAttack.cityGateAsk();
    }
}

 代码清单 8 Director:通过接口方法注入革离扮演者


     由于通过接口注入需要额外声明一个接口,增加了类的数目,而且它的效果和属性注入并无本质区别,因此我们不提倡这种方式。

通过容器完成依赖关系的建立

      虽然MoAttack和LiuDeHua实现了解耦,无需关注实现类的实例化工作,但这些工作在代码中依然存在,只是转移到Director中而已,导致导演的权力非常大,潜规则不断滋生。假设某一制片人想改变这一局面,在相中某个剧本后,通过一个“海选”或者第三公正中介来选择导演、演员,让他们各司其职,那剧本、导演、演员就都实现解耦了。
      所谓媒体“海选”和中介机构在程序领域即是一个第三方容器,它帮助我们完成类的初始化和装配工作,让我们从这些底层的实现类实例化,依赖关系的装配中脱离出来,专注于更有意思的业务代码的编写工作,那确实是挺惬意的事情。Spring就是这样一个容器,它通过配置文件描述类之间的依赖关系,下面是Spring配置文件的对以上实例进行配置的样式代码:

<beans>
      <bean id="geli" class="com.baobaotao.LiuDeHua"></bean>
      <bean id="moAttack" class=" com.baobaotao.MoAttack">
            <property name="geli">
                   <ref bean="geli"/>
            </property>
      </bean>
</beans>

 
     通过new XmlBeanFactory(“beans.xml”)等方式即可启动容器,在容器启动时,Spring根据配置文件的描述信息,通过Java的反射机制自动实例化Bean并完成依赖关系的建立,从容器中即可返回准备就绪的Bean实例,以待后续的使用。

小结:

      随着Spring的广泛应用,IoC的概念被越来越多的提及,可是很多说者因为IoC本身的晦涩往往并不清楚其中的意义,我们通过时下叫座的贺岁大片《墨攻》来解释IoC的概念,相信对加深IoC概念的理解有所补益。

 

分享到:
评论

相关推荐

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

    首先,让我们理解Java反射。反射允许程序在运行时检查类、接口、字段和方法的信息,并且能够动态地创建对象和调用方法。在IOC中,反射用于在运行时解析依赖关系,根据配置创建对象。以下是一些关键的反射API: 1. `...

    精通Spring4.x企业应用开发实战

    个人印象比较深刻是在讲解IOC的时候举了一个《墨攻》例子,通过导演和演员的关系很巧妙地说明了什么是IOC,为什么要IOC。虽然Spring已经更新到5点几了,但是通过精读这本书,对于一名实习生,也够应对面试了。

    Spring 3.x 企业应用开发实战.pdf

    ### Spring 3.x 企业...通过上述内容可以看出,《Spring 3.x 企业应用开发实战》这本书不仅详细介绍了IoC容器的相关概念,而且还通过具体的案例帮助读者深入理解IoC的核心思想。这对于初学者来说是非常有价值的资源。

    《墨攻》中的企业管理

    《墨攻》中的企业管理 中国有句成语叫作“墨守成规”,是比喻拘泥成见而不善于机变。在这样的一个贬义词的影响下,我一直都以为墨家学派真如成语中所形容的那样,思维定式,不善于变通。 初见《墨攻》,随即就联想

    金蝶KIS专业版成就利华化工墨攻之路

    无论是存货计量、库存控制、回款风险管理,还是信息统计和外币处理,金蝶KIS专业版都提供了有针对性的解决方案,助力企业像《墨攻》中的主角一样,以智慧和策略在激烈的市场环境中立于不败之地。

    91玩游戏平台分析[整理].pdf

    总体来看,91wan游戏运营平台总计超过145个服务器,每周平均开设10个以上的新服。 在平台服务方面,91wan拥有庞大的注册会员数,但可能存在水分。论坛成为玩家交流和解决问题的重要渠道,尤其在服务器出现问题时,...

    (完整word)电脑装机配置单模版.doc

    下面将对每个配置单模板中的组件进行详细解释,帮助读者更好地理解电脑装机配置单模板的重要性。 CPU 在电脑装机配置单模板中,CPU 是最重要的组件之一。AMD 四核 X4 640 和 AMD 四核 A8-6600K 都是高性能的 CPU,...

    七年级历史上册 第9课 中华文化的勃兴(二)课前自主学习(无答案) 新人教版 试题.doc

    【知识点详解】 1. 孔子及其思想与教育贡献 孔子,春秋晚期人,是中国儒家学派的创始人。他提出的“仁”的学说,认为“仁”包含了所有美德,是个人...这些内容对于理解中国古代文化和哲学思想的发展具有重要意义。

    山东省淄博市淄川区般阳中学2019_2020学年高二历史上学期期中试题202001090142

    5. 墨家思想与历史背景:战国时期,思想界确实存在“百家争鸣”的局面,墨家主张非攻、兼爱,与电影《墨攻》的故事背景相符。 6. 法家韩非子的主张:他的法治思想得到了秦王嬴政的赏识,因为这符合当时加强中央集权...

    福建省南平市水东学校2014-2015学年七年级历史上学期期中试题

    17. **墨家思想**:墨家学者反对掠夺战争,主张非攻,与电影《墨攻》中的情节相符。 18. **无为而治**:这是道家思想,主张顺应自然,不强行干预。 19. **余秋雨的《古堰沉思录》**:提到的“无为而治”思想,是...

    UI设计+易语言+图书管理和查询系统+总结报告书

    UI设计应遵循易用性原则,确保用户界面清晰、直观,功能布局合理,图标和按钮设计易于理解。设计过程中可能包括了窗口布局、控件选择、色彩搭配以及响应式设计,以适应不同屏幕尺寸的设备。设计师可能会使用Sketch、...

    易语言图书管理和查询系统

    通过研究这个系统,可以深入理解易语言的编程技巧,以及如何运用数据库技术实现图书管理功能。 总的来说,这个《易语言图书管理和查询系统》展示了易语言在实际项目中的应用,同时也体现了数据库管理在信息时代的...

    DiffMerge 强大的文件比较工具

    这个工具的核心功能是对比两个或多个文件的差异,并以清晰、高亮的方式显示出来,使得用户能快速理解文件间的异同。标题中的“强大的文件比较工具”正是对DiffMerge核心特性的精准概括,它提供了高效且直观的比较...

    linux系统安装

    在IT领域,Linux系统安装是每位技术爱好者或专业IT人士必备的技能之一...整个安装过程中,理解基本的系统引导原理和熟悉安装工具的使用是关键。同时,利用额外的资源和工具,可以更好地适应不同的安装需求和个人偏好。

    福建省南安市实验中学2015_2016学年七年级历史上学期期中试题新人教版

    17. 墨家思想:墨家学者主张兼爱非攻,反对掠夺战争,与电影《墨攻》中的情节相符。 18. 《孙子兵法》:"知己知彼,百战不殆"是兵家经典《孙子兵法》中的著名军事策略。 19. 彩陶器皿用途:题中未提供选项的图片,...

    Inno Setup 编译器

    Inno Setup 是一款免费的安装制作工具。简洁而强大,支持 pascal 脚本,既有适合新手的脚本向导,也有适合高手的的脚本编辑器。是一个不可多得的安装制作软件! 汉化版中已添加了原版安装文件中没有的安装程序加密...

    安装nvm过程.docx

    安装 NVM 过程详解 Node.js 是一个基于 JavaScript 语言的 Runtime环境,它允许开发者使用 JavaScript 语言来编写服务器端代码。但是,Node.js 的版本管理一直是一个让开发者头疼的问题。幸好,有一个非常优秀的...

    C++学生信息管理系统+读写txt

    【C++学生信息管理系统+读写txt】 C++学生信息管理系统是一种常见的编程实践项目,它旨在帮助用户存储、检索和管理学生的基本信息。这个系统通常包括添加新学生、删除学生、查询学生信息以及修改学生信息等功能。...

Global site tag (gtag.js) - Google Analytics