IOC
知识点-工厂模式解耦
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(工厂模式解耦)
- 原始方式
- 方式: 创建类, 直接根据类new对象
- 优点: 好写, 简单
- 缺点: 耦合度太高, 不好维护
- 接口方式
- 方式: 定义接口, 创建实现类. 接口=子类的对象
- 优点: 耦合度相对原始方式 减低了一点
- 缺点: 多写了接口, 还是需要改源码 不好维护
- 自定义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.小结
- 程序初始化的时候, 解析xml, 全部解析好 存到容器【Map】里面
- 把bean标签的id作为map的key, bean标签的class属性值反射创建对象之后作为map的value
- 要使用对象, 不再直接new了, 而是从容器里面直接取
知识点-IOC概念
1.目标
- 能够理解IOC概念
2.路径
- 分析和IOC的引入
- 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.小结
- IOC: 控制反转. 对象的使用者不是创建者. 把对象的创建和管理, 交给Spring框架. 要使用的时候, 直接从Spring工厂(容器)里面取
- 作用: 解耦.