Spring-IOC诞生的缘由与其技术思想

Spring-IOC诞生的缘由与其技术思想

  • 友情提醒
    • 本文章 并没有深挖spring-IOC的源码与其具体的使用方法,只是讲述了spring-IOC所解决的问题以及其所应用的技术思想,并通过具体的实例来让读者对该技术思想有一个整体上的认识,方便读者今后对spring-IOC的深究。
  • 本文所使用的代码如下,若有所需请自行下载:
  • 若图片中的文字太小,可以将该图片另存为保存下来,在进行查看

1. 什么是程序耦合

  • 在介绍ioc之前,我们首先要了解一下什么是程序的耦合性:

  • 耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。

  • 模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。

  • 耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合。它有如下分类:

    1. 内容耦合。当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
    2. 公共耦合。两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
    3. 外部耦合。一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。
    4. 控制耦合。一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合。
    5. 标记耦合。若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间存在一个标记耦合。
    6. 数据耦合。模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。
    7. 非直接耦合。两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。
  • 总述

    • 耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。
  • 内聚与耦合

    • 内聚 标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。
    • 耦合 是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。
    • 程序讲究的是 低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却要不那么紧密。
    • 内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合。
  • 在我们的开发中,有些依赖关系是必须的,而有些依赖关系是可以通过优化代码来解除的

    • spring的IOC就是为此而诞生的
  • 接下来,为了使大家更好的理解IOC技术思想,我们就使用一个简单的实例来进行演示

    • 注意:这里的解耦演示并不使用spring-IOC技术,而是由我们自己编写代码来实现解耦。

2. 原始代码

  • 在这里我按照 三层架构 写了一个简单的程序,其 目录结构程序代码 如下(由于这里只是为了演示,所以并没有去真正的调用数据库):
    01_原始代码

  • 原始代码运行结果
    02_原始代码运行结果

  • 由以上代码我们可以看到,在AccountServiceImpl.java中,我们new了一个AccountDaoImpl.java的对象,而又在Client.java中,new了一个AccountServiceImpl.java的对象。

  • 以这种方式来相互调用貌似看起来在正常不过了,但是这里存在一个致命的问题:一旦在程序中缺失了AccountServiceImpl.java或AccountDaoImpl.java,那么我们的程序将在编译期就会发生报错,如图:
    03_编译期报错

  • 而这种 编译期依赖关系,应该在我们的开发中尽可能的降到最低

  • 那么我们应该如何去解决这个问题呢?

  • 或许我们可以通过jdbc找到答案

    • 还记得我们在 注册驱动 时,是通过 反射 来进行的吗?代码如下:
    • Class.forName("com.mysql.jdbc.Driver")
    • 这样写的好处就是,我们的类不再依赖具体的驱动类,此时就算删除mysql的驱动jar包,依然可以编译成功(但是此时运行肯定是失败的)
    • 但是在这里又会产生一个新的问题:mysql驱动的全限定类名字符串是在java类中写死的,一旦要发生改变,就需要去修改源码。对于这个问题也很简单,使用配置文件即可。
  • 有了以上引导,相信大家也不难想出解决方法了

    • 在实际的开发中,我们可以将三层中的类使用配置文件配置起来(即将其全限定类名写入配置文件中),当启动服务器应用加载的时候,让其中一个类的方法去读取配置文件,然后通过反射来创建对象并将其存储到容器中。这样的话,当我们需要使用的时候,就可以从容器中直接取得。
    • 那么,这个读取配置文件,创建和获取三层对象的类就是工厂
    • 这种方法也就是我们常说的 工厂模式减耦

3. 改进后的代码

  • 那么接下来我们将运用这种方式来改善上述的原始代码,经过修改后的目录结构和代码如下:
    04_改进后的代码
  • 由上述改进后的代码我们可知
    • 我们将dao层和service层中所有实现类的全限定类名以键值对的方式存储在了名为Bean.properties的配置文件中
    • 然后在工厂类 BeanFactory.java 中加载了该配置文件,使用反射根据配置文件中的全限定类名创建了相应的对象,并将这些对象加入map容器中以便调用
    • 最后在 AccountServiceImpl.java类 和 Client.java类 中通过BeanFactory工厂来获取相关的对象。
  • 接下来让我们试运行一下:
    05_成功运行
  • 成功运行。
  • 再让我们看看,当删掉AccountDaoImpl.java实现类后,编译是否会出错
    06_看改进后的代码是否出现编译错误
  • 可以看到并没有出现编译错误。
  • perfect!!!我们已经完美解决了 编译期依赖 的问题。

4. 总结

  • 通过上述示例的演示,相信大家对于spring-ioc所应用的思想已经有了自己的判断,在这里我来稍微总结一下:
  • 其实spring的IOC就是基于工厂模式的技术思想来实现的,它与上述的改进代码一样,都是为了对程序进行 减耦 操作,只不过IOC相比于我们自己的代码进行了更为系统化的管理改进,提供了更为强大的功能。
  • 而之所以叫做 控制反转 是因为:
    • 在原来我们获取对象时,都是采用new的方式创建的,相对于 调用程序本身 而言是 主动的,但是由于现在有了IOC(工厂)的加入,我们的对象就由IOC(工厂)来创建并由IOC(工厂)帮助我们将对象托管到了容器中,此时相对于 调用程序本身 而言,就 不再具有创建对象的主动权,而是 被动的 去容器中获取已经被创建好的对象,这种以被动的方式来获取对象的思想就是控制反转,即spring框架的核心之一。
posted @ 2022-03-08 18:49  浅易深  阅读(93)  评论(0编辑  收藏  举报