Spring(2) IoC 的概念和作用
一、程序的耦合和解耦
1.耦合
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合。
它有如下分类:
(1)内容耦合。当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
(2)公共耦合。两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
(3) 外部耦合 。一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。
(4) 控制耦合 。一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合。
(5)标记耦合 。若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间存在一个标记耦合。
(6) 数据耦合。模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。
(7) 非直接耦合 。两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。
总结:
耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。
内聚与耦合
内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。 程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却要不那么紧密。内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合。
2.解决耦合的思路
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合。
3.JDBC耦合解耦实例分析
(1)我们最早学习mysql数据库链接并遍历数据的例子如下:
package com.javaxhb.demo; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; /** * jdbcdemo类 * * @author mr.wang * */ public class JdbcDemo { public static void mian(String[] args) throws SQLException { // 声明Connection对象 Connection con; // 驱动程序名 String driver = "com.mysql.jdbc.Driver"; // URL指向要访问的数据库名mydata String url = "jdbc:mysql://localhost:3306/sqltestdb"; // MySQL配置时的用户名 String user = "root"; // MySQL配置时的密码 String password = "123456"; // 1.注册驱动 DriverManager.registerDriver(new com.mysql.jdbc.Driver()); // 2.获取连接 con = DriverManager.getConnection(url, user, password); // 3.获取预处理到sql语句 Statement statement = con.createStatement(); // 要执行的SQL语句 String sql = "select * from emp"; // 4.获取结果集 ResultSet rs = statement.executeQuery(sql); // 5.遍历结果集 String job = null; String id = null; while (rs.next()) { // 获取stuname这列数据 job = rs.getString("job"); // 获取stuid这列数据 id = rs.getString("ename"); // 输出结果 System.out.println(id + "\t" + job); } rs.close(); con.close(); } }
但是对于1.注册驱动这一步操作我们用到最多到还是Class.forName("com.mysql.jdbc.Driver");为什么不用不使用 DriverManager 的 register 方法呢?通过上面我们对耦合概念到了解发现, DriverManager 的 register方法依赖到是数据库到具体驱动类,耦合性太强,我们更换成oracle数据岂不是要修改源码?而且如果我们没有引入mysql数据库相应到jar包程序在编译时也是会报错的,这这种结果 不是我们想要到的。
(2)解决耦合的思路
通过上面例子我们发现JDBC例子存在耦合现象,我们通过Class.forName("com.mysql.jdbc.Driver")反射来注册驱动,这样的好处是我们不在依赖mysql驱动类,即使删除驱动包依然能够编译。但是这样做mysql的全限定类别依然在Java类中写死,需要修改的话还需要修改源码。
为了解决这个问题,我们可以采用配置文件的方式进行配置即可。具体实例可以参考:https://www.cnblogs.com/xhbJava/p/12157998.html
4.代码分层示例
(1)分层示例
/** * 账户的业务层实现类 */ public class AccountServiceImpl implements IAccountService { private IAccountDao accountDao = new AccountDaoImpl(); public void saveAccount(){ accountDao.saveAccount(); } }
我们之前进行代码分层调用,在业务层调用持久层都采用new的形式创建dao。那么此时业务层依赖持久层的实现类和接口,如果持久层不存在,那么我们编译时将不能通过。这种编译期的依赖我们在开发中应该杜绝,开发过程中有些依赖是必须的,有些依赖我们可以通过优化代码来解除,比如上面的例子。
(2)解耦思路
我们在实际代码分层中,我们可以采用工厂模式来解耦。在实际开发中可以把每层依赖的对象使用配置文件配置起来,服务器启动的时候通过一个类进行加载读取这些配置文件创建对象,并且把对象存储起来(我们可以通过Map存储),在每层调用的时候可以直接拿来即用。此时,读取配置文件,创建和获取层对象的类就是工厂。
(3)工厂模式示例
- 先创建一个maven工程
- 创建service层接口和实现类
package com.xhbjava.service; /** * 账户业务层接口 * * @author mr.wang * */ public interface IAccountService { /** * 模拟保存账户 */ void saveAccount(); }
package com.xhbjava.service.impl; import com.xhbjava.service.IAccountService; /** * 业务层实现类 * * @author mr.wang * */ public class AccountServiceImpl implements IAccountService { private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao"); public void saveAccount() { accountDao.saveAccount(); } }
- 创建持久层接口和实现层
package com.xhbjava.dao; /** * 持久层接口 * @author mr.wang * */ public interface IAccountDao { /** * 模拟保存账户 */ void saveAccount(); }
package com.xhbjava.dao.impl; import com.xhbjava.dao.IAccountDao; /** * 持久层实现类 * @author mr.wang * */ public class AccountDaoImpl implements IAccountDao { public void saveAccount() { System.out.println("保存了账户"); } }
- 创建工厂类及配置文件
accountService=com.xhbjava.service.impl.AccountServiceImpl
accountDao=com.xhbjava.dao.impl.AccountDaoImpl
package com.xhbjava.factory; import java.io.InputStream; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * 创建bean的工厂 Bean:在计算机英语中,有可重用组件的含义。 JavaBean:用java语言编写的可重用组件。 javabean > 实体类 * * 它就是创建我们的service和dao对象的。 * * @author mr.wang 思路分析 1.创建配置文件,配置service和dao, * 配置的内容:唯一标识=全限定类名(key=value),类似我们读取数据库配置 2.读取配置文件内容,通过反射创建对象 * */ public class BeanFactory { // 定义一个Properties对象 private static Properties props; // 定义一个Map,用于存放我们要创建的对象。我们把它称之为容器 private static Map<String, Object> beans; //使用静态代码块为Properties对象赋值 static { try { //实例化对象 props = new Properties(); //获取properties文件的流对象 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); props.load(in); //实例化容器 beans = new HashMap<String,Object>(); //取出配置文件中所有的Key Enumeration keys = props.keys(); //遍历枚举 while (keys.hasMoreElements()){ //取出每个Key String key = keys.nextElement().toString(); //根据key获取value String beanPath = props.getProperty(key); //反射创建对象 Object value = Class.forName(beanPath).newInstance(); //把key和value存入容器中 beans.put(key,value); } }catch(Exception e){ throw new ExceptionInInitializerError("初始化properties失败!"); } } /** * 根据bean的名称获取对象 * * @param beanName * @return */ public static Object getBean(String beanName) { return beans.get(beanName); } }
二、IOC(Inversion Of Control)的概念和作用
(1)IOC概念
先举一个生活中的例子,比如小明想吃鱼香肉丝,在网络不是很发达,没有外卖,饭店太贵,小明就开始动手做鱼香肉丝,首先准备食材,准备厨具,然后根据烹饪步骤制作鱼香肉丝,这时是小明自己“主动”做的;但是现在有外卖,小明不想自己做,直接打开手机,下单,点外卖就行。请注意,小明并没有“主动”制作鱼香肉丝,而是饭店制作的,但也达到了小明的要求。这个例子生活中很常见,却包含着控制翻转的思想。
回过头,我们回顾上面工厂解耦的例子:
我们有很多对象,工厂解耦时我们根据需求选择map进行存储对象,我们用的这个map就是容器。
工厂解耦的例子中,我们的工厂就是负责给我们从容器中获取制定对象或生产对象的类。这时候我们获取对象的方式发生了变化。
原来我们在代码分层中获取对象通过new的方式,这是主动创建对象的。通过工厂模式后,我们在分层中获取相应的对象通过工厂获取,这时工厂负责帮助我们查找或者创建对象,这时被动的。这就和我们刚才小明吃鱼香肉丝的例子不谋而合。这种被动接收的方式获取对象的思想就是控制反转,它是 spring 框架的核心之一。
控制反转:控制反转是一种通过描述(在Java中可以是xml或者注解)并通过第三方去生产或获取特定对象的方式。
(2)作用
通过我们例子和描述我们可以明确IOC的主要作用就是帮助我们削减计算机程序中的耦合即解除或者削弱我们代码中的依赖关系。