Spring专栏1 - IOC

IOC

知识点-工厂模式解耦

1.目标

  • 能够使用工厂模式进行解耦

2.路径

  1. 程序耦合的概述

  2. 使用工厂模式解耦

3.讲解

3.1程序的耦合

3.1.1 程序耦合的概述

​ 耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。 耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。
​ 在软件工程中, 耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计
应使类和构件之间的耦合最小。 软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。 划分模块的一个
准则就是高内聚低耦合。
总结:
​ 耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。内聚与耦合内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。耦合是软件
结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。 程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之
间的相互依存度却要不那么紧密。

​ 内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他
模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合。

3.1.2在代码中体现

​ 早期我们的 JDBC 操作(面向接口的编程),注册驱动时,我们为什么不使用 DriverManager 的 register 方法,而是采
用 Class.forName 的方式?

​ 原因就是: 我们的类依赖了数据库的具体驱动类(MySQL) ,如果这时候更换了数据库(比如 Oracle) ,
需要修改源码来重新数据库驱动。这显然不是我们想要的。

/**
* 程序的耦合
* 耦合:程序间的依赖关系
* 包括:
* 		类之间的依赖
* 		方法间的依赖
* 解耦:
* 		降低程序间的依赖关系
* 实际开发中:
* 		应该做到:编译期不依赖,运行时才依赖。
* 解耦的思路:
*		第一步:使用反射来创建对象,而避免使用 new 关键字。
*		第二步:通过读取配置文件来获取要创建的对象全限定类名
*/
public class JdbcDemo1 {
    
    
    public static void main(String[] args) throws Exception{
    
    
        //1.注册驱动 
        //DriverManager.registerDriver(new Driver());//第一种注册驱动的方式
        //第一种注册驱动的方式,缺点是耦合性强
        
        Class.forName("com.mysql.jdbc.Driver");//这是第二种注册驱动的方式
        //第二种注册驱动的方式,缺点是"字符串硬编码",可以使用配置文件解决
        
        //2.获取连接
        //3.获取操作数据库的预处理对象
        //4.执行 SQL,得到结果集
        //5.遍历结果集
        //6.释放资源
    }
}

第一种耦合:一个类直接依赖另外一个类

第二种耦合:将字符串直接写死在Java代码中(字符串的硬编码)

3.1.3解决程序耦合的思路

产生类与类之间的耦合的根本原因是:在一个类中new了另外一个类的对象

解决耦合的思路:不在类中创建另外一个类的对象,但是我们还需要另一个类的对象;由别人(Spring)把那个类的对象创建好之后给我用就可以了

​ 当是我们讲解 jdbc 时,是通过反射来注册驱动的,代码如下:

Class.forName("com.mysql.jdbc.Driver");//此处只是一个字符串

​ 此时的好处是,我们的类中不再依赖具体的驱动类,此时就算删除 mysql 的驱动 jar 包,依然可以编译(运行就不要想了没有驱动不可能运行成功的) 。

​ 同时,也产生了一个新的问题, mysql 驱动的全限定类名字符串是在 java 类中写死的,一旦要改还是要修改源码。解决这个问题也很简单,使用配置文件配置

3.2.自定义IOC(工厂模式解耦)

  1. 原始方式
    • 方式: 创建类, 直接根据类new对象
    • 优点: 好写, 简单
    • 缺点: 耦合度太高, 不好维护
  2. 接口方式
    • 方式: 定义接口, 创建实现类. 接口=子类的对象
    • 优点: 耦合度相对原始方式 减低了一点
    • 缺点: 多写了接口, 还是需要改源码 不好维护
  3. 自定义IOC
    • 方式: 使用对象的话, 不直接new()了,直接从工厂里面取; 不需要改变源码
3.2.1原始方式

代码略

3.2.2接口方式
  • UserService.java

public interface UserService {
    
    
    String getName();
}
  • UserServiceImpl.java

public class UserServiceImpl implements UserService{
    
    
    public void init(){
    
    
        System.out.println("UserServiceImpl对象创建了...");
    }

    public void destroy(){
    
    
        System.out.println("UserServiceImpl对象销毁了...");
    }

    @Override
    public String getName() {
    
    
        return "周杰棍";
    }
}
  • TestSpring.java
public class TestSpring {
    
    
    public void test01() {
    
    
        AccountService accountService = new AccountServiceImpl();
        accountService.getName();
    }

}
3.2.3自定义IOC(使用工厂模式解耦)
3.2.2.1工厂模式解耦思路

​ 在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候, 让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。那么,这个读取配置文件, 创建和获取三层对象的类就是工厂

3.2.2.2实现
  • 添加坐标依赖
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>

    <!-- 解析 xml 的 dom4j -->
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
    <!-- dom4j 的依赖包 jaxen -->
    <dependency>
        <groupId>jaxen</groupId>
        <artifactId>jaxen</artifactId>
        <version>1.1-beta-8</version>
    </dependency>
</dependencies>
  • UserService.java

public interface UserService {
    
    
    String getName();
}
  • UserServiceImpl.java

public class UserServiceImpl implements UserService{
    
    
    public void init(){
    
    
        System.out.println("UserServiceImpl对象创建了...");
    }

    public void destroy(){
    
    
        System.out.println("UserServiceImpl对象销毁了...");
    }

    @Override
    public String getName() {
    
    
        return "周杰棍";
    }
}
  • Client.java

public class UserServiceImpl implements UserService{
    
    
    @Override
    public String getName() {
    
    
        return "张三";
    }
}
  • BeanFactory.java

    下面的通过 BeanFactory 中 getBean 方法获取对象就解决了我们代码中对具体实现类的依赖。


import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class BeanFactory {
    
    
    private static Map<String,Object> map = new HashMap();
    static {
    
    
        try {
    
    
            //将读取配置文件的代码,放到静态代码块中
            //解析配置文件beans.xml,根据id的值,获取class的值
            SAXReader saxReader = new SAXReader();
            InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
            Document document = saxReader.read(is);
            //获取所有的bean标签
            List<Element> elementList = document.selectNodes("//bean");
            if (elementList != null) {
    
    
                for (Element element : elementList) {
    
    
                    //获取标签的id属性
                    String idAttr = element.attributeValue("id");
                    String className = element.attributeValue("class");
                    //根据类的全限定名,创建出该类的对象
                    Object obj = Class.forName(className).newInstance();
                    //以id作为key,以对象作为value存入map中
                    map.put(idAttr,obj);
                }
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
    public static Object getBean(String id){
    
    
        try {
    
    
            //根据传入的id,获取到对应的className
            Object obj = map.get(id);
            if (obj != null) {
    
    
                return obj;
            }
            throw new RuntimeException("找不到对应的类!!!");
        } catch (Exception e) {
    
    
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }
}
  • spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean class="com.itheima.service.impl.UserServiceImpl" id="userService"></bean>
</beans>

4.小结

  1. 程序初始化的时候, 解析xml, 全部解析好 存到容器【Map】里面
  2. 把bean标签的id作为map的key, bean标签的class属性值反射创建对象之后作为map的value
  3. 要使用对象, 不再直接new了, 而是从容器里面直接取

知识点-IOC概念

1.目标

  • 能够理解IOC概念

2.路径

  1. 分析和IOC的引入
  2. IOC概述

3.讲解

3.1分析和IOC的引入

上一小节我们通过使用工厂模式,实现了解耦。
它的核心思想就是:
​ 1、通过Bean工厂读取配置文件使用反射创建对象。
​ 2、把创建出来的对象都存起来,当使用者需要对象的时候,不再自己创建对象,而是调用Bean工厂的方法从容器中获取对象
这里面要解释两个问题:
​ 第一个:存哪去?
​ 分析:由于我们是很多对象,肯定要找个集合来存。这时候有 Map 和 List 供选择。
​ 到底选 Map 还是 List 就看我们有没有查找需求。有查找需求,选 Map。
​ 所以我们的答案就是:在应用加载时,创建一个 Map,用于存放三层对象。我们把这个 map 称之为容器。
​ 第二个: 什么是工厂?
​ 工厂就是负责给我们从容器中获取指定对象的类。这时候我们获取对象的方式发生了改变。
​ 原来:我们在获取对象时,都是采用 new 的方式。 是主动的。

3.2IOC概述

​ IOC(inversion of control)的中文解释是“控制反转”,对象的使用者不是创建者. 作用是将对象的创建 反转给spring框架来创建和管理。

​ 控制反转怎么去理解呢。 其实它反转的是什么呢,是对象的创建工作。 举个例子:平常我们在servlet或者service里面创建对象,都是使用new 的方式来直接创建对象,现在有了spring之后,我们就再也不new对象了,而是把对象创建的工作交给spring容器去维护。我们只需要问spring容器要对象即可

​ ioc 的作用:削减计算机程序的耦合(解除我们代码中的依赖关系)。

4.小结

  1. IOC: 控制反转. 对象的使用者不是创建者. 把对象的创建和管理, 交给Spring框架. 要使用的时候, 直接从Spring工厂(容器)里面取
  2. 作用: 解耦.

猜你喜欢

转载自blog.csdn.net/malipku/article/details/113531702
今日推荐