耦合
耦合:程序间的依赖关系
耦合包括:
- 类与类之间的依赖关系
- 方法与方法之间的依赖关系
解耦
解耦:降低程序间的依赖关系
一、由JDBC引出解耦
先上一段熟悉的jdbc代码
//1.导入驱动jar包
//2.注册驱动
DriverManager.registerDriver(newcom.mysql.jdbc.Driver());
//3.获取数据库连接对象
Connectionconn=DriverManager.getConnection("jdbc:mysql://localhost:3306/contest?useUnicode=true&characterEncoding=UTF-8", "user", "password");
//4.定义SQL语句
Stringsql="update hello set age=30 where id=1";
//5.获取执行SQL的对象Statement
Statementstmt=conn.createStatement();
//6.执行SQL
intresult=stmt.executeUpdate(sql);
//7.处理结果
System.out.println(result);
//8.释放资源
stmt.close();
conn.close();
显然,当我们不导入mysql的驱动jar包,这段代码在编译期就会报错,因为我们用到了com.mysql.jdbc.Driver()
,会导致类的独立性很差。如何解决上诉问题?
Class.forName("com.mysql.jdbc.Driver");
,
我们常常使用上诉代码来加载驱动,这样编译期不再依赖于某个具体的驱动类,而依赖一个字符串。查看com.mysql.jdbc.Driver
类的源码发现静态代码块(当类被加载进内存,静态代码块会自动执行)
static {
try {
DriverManager.registerDriver(newDriver());
} catch (SQLExceptionvar1) {
thrownewRuntimeException("Can't register driver!");
}
}
可知,Class.forName("com.mysql.jdbc.Driver");
本质上还是DriverManager.registerDriver(Driver driver)
,但这样封装一下,不仅提高了代码的独立性(不导jar包,编译期不报错,运行时会报错),而且简化了代码。
我们知道字符串在代码中是写死的,当我们想要更换为其他数据库的驱动时,还要更改代码,十分不便。如何解决上诉问题?
jdbc.properties
url=jdbc:mysql:///contest
user=xxx
password=xxx
driver=com.mysql.jdbc.Driver
我们可以通过读取配置文件来获取需要的数据
//1.创建properties集合类
Propertiespro=newProperties();
//获取src目录下文件的方式--> ClassLoader 类加载器(可以加载字节码文件进内存,并且可以获取src目录下资源的路径)
ClassLoaderclassLoader=JDBCUtils.class.getClassLoader();
URLres=classLoader.getResource("jdbc.properties");
Stringpath=res.getPath();
//2.加载文件
pro.load(newFileReader(path));
//3.获取数据,并赋值
url=pro.getProperty("url");
user=pro.getProperty("user");
password=pro.getProperty("password");
driver=pro.getProperty("driver");
//4.注册驱动
Class.forName(driver);
综上操作,可以实现编译期不依赖某个类,运行时才依赖,降低了类与类之间的依赖关系,实现了解耦
解耦的思路
- 使用反射来创建对象,而避免使用new关键字
- 通过读取配置文件来获取要创建的对象的全限定类名
二、将解耦思想运用到三层架构
三层架构
因为面向接口编程,所以三层架构的每一层我们都写了一个接口,在impl包
写实现类,使用的是实现类对象
步骤1:创建持久层接口
packagecn.upeveryday.dao;
/**
* 账户的持久层接口
*/
publicinterfaceIAccountDao {
voidsaveAccount();
}
步骤2:创建持久层实现类
packagecn.upeveryday.dao.impl;
importcn.upeveryday.dao.IAccountDao;
/**
* 账户的持久层实现类
*/
publicclassAccountDaoImplimplementsIAccountDao {
publicvoidsaveAccount() {
System.out.println("保存成功!");
}
}
步骤3:创建业务层接口
packagecn.upeveryday.service;
/**
* 账户的业务层接口
*/
publicinterfaceIAccountService {
/**
* 模拟保存
*/
voidsaveAccount();
}
步骤4:创建业务层实现类
packagecn.upeveryday.service.impl;
importcn.upeveryday.dao.IAccountDao;
importcn.upeveryday.service.IAccountService;
/**
* 账户的业务层实现类
*/
publicclassAccountServiceImplimplementsIAccountService {
//业务层调用持久层
privateIAccountDaoaccountDao=newAccountDaoImpl();
publicvoidsaveAccount() {
accountDao.saveAccount();
}
}
步骤5:创建表现层
packagecn.upeveryday.ui;
importcn.upeveryday.factory.BeanFactory01;
importcn.upeveryday.factory.BeanFactory02;
importcn.upeveryday.service.IAccountService;
/**
* 模拟一个表现层,用于调用业务层
*/
publicclassClient {
publicstaticvoidmain(String[] args) {
AccountServiceImplservice=newAccountServiceImpl();
service.saveAccount();
}
}
三层架构解耦分析
我们知道,在三层架构中业务层调用持久层,表现层调用业务层
由解耦思路:
- 使用反射来创建对象,而避免使用new关键字
- 通过读取配置文件来获取要创建的对象的全限定类名
可以使用工厂模式解耦
三、工厂模式解耦
/**
* 一个创建Bean对象的工厂
* Bean:在计算机英语中,是可重用组件的意思
* 可重用组件:业务层、持久层都是可重用组件(表现层调用业务层,业务层调用持久层)
* javabean:用java语言编写的可重用组件
* javabean范围 > 实体类范围
* BeanFactory就是用来创建service和dao对象的:
* 1.需要一个配置文件来配置我们的service和dao(配置文件可以是xml或properties)
* 配置的内容:唯一标识符=全限定类名(key=value)
* 2.读取配置文件内容,反射创建对象
* 配置文件可以是xml,也可以是properties
*/
单例:从始至终只有一个实例对象。例如:servlet
多例:有多个对象
如果是多例的,对象会被创建多次,类中的成员变量每次都会被初始化,因此不存在线程安全问题
单例,对象只被创建一次,类中的成员变量也只被初始化一次,则会产生线程安全问题
对象被创建多次,执行效率没有单例对象高。而且我们在service和dao中并没有定义成员变量,因此不存在线程安全问题
工厂模式实现了由自己控制资源转变为工厂factory控制资源,从而实现控制反转IOC(Inversion of Control),解决了程序间的依赖关系,spring是使用配置的方式实现工厂模式进而实现解耦,而不需要我们自己编写beanFactory代码
IOC只能解决程序间的依赖关系