https://www.bilibili.com/video/av47952931
p9~14
程序的耦合与解耦 (以jdbc注册驱动为例)
jdbc操作中,注册数据库驱动时,有2种方法
1 | // 方法1 |
在pom.xml中添加了依赖的情况下都可以正常运行
1 | <dependencies> |
但是,如果去掉这段依赖
方法1报Error,无法通过编译
1 | Error:(26, 56) java: 程序包com.mysql.jdbc不存在 |
而方法2报Exception,可以通过编译(无法运行)
1 | Exception in thread "main" java.lang.ClassNotFoundException: com.mysql.jdbc.Driver |
因为方法1依赖一个具体的驱动类,而方法2用反射,依赖的只是一个字符串
但是这个字符串仍是写死在代码里的。应该写到配置文件里去,进一步减少耦合
实际开发中应该做到:
编译期不依赖,运行时才依赖
解耦的思路:
第一步:使用反射来创建对象,而避免使用new关键字
第二步:通过读取配置文件来获取要创建的对象全限定类名
工厂模式
原始的分层实现方法
- Client : 模拟一个表现层,用于调用业务层
- IAccountService : 账户业务层的接口
- AccountServiceImpl : 账户的业务层实现类
- IAccountDao : 账户的持久层接口
- AccountDaoImpl : 账户的持久层实现类
其中,有两个依赖关系
- 表现层调用业务层时
1
2
3
4
5
6public class Client {
public static void main(String[] args) {
IAccountService as = new AccountServiceImpl();
as.saveAccount();
}
} - 业务层调用持久层时都用到了new,耦合度高
1
2
3
4
5
6public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
public void saveAccount(){
accountDao.saveAccount();
}
}
如果此时把AccountDaoImpl的代码删了,Service就报错了。和前面jdbc中编译期的错误一样
如何解除这种依赖?
Bean & BeanFactory
Bean —— 可重用组件
eg:一个Dao可能被多个Service使用,一个Service可能被多个Servlet使用,它们是可重用的
JavaBean —— 用java语言编写的可重用组件
BeanFactory —— 创建Bean对象的工厂
eg:创建Dao和Service对象
要实现这个工厂,类似前面jdbc,
1.需要一个配置文件来配置service和dao
内容:唯一标识=全限定类名(key=value)
配置文件可以是xml也可以是properties
2.读取配置文件中配置的内容,通过==反射==创建对象
工厂模式解耦
在resources中新建beans.properties配置文件(此处用properties因为简单,Spring中用的是xml)
1 | accountService = com.itheima.service.impl.AccountServiceImpl |
创建BeanFactory类读取properties文件
1 | public class BeanFactory { |
注意:
- 读配置文件时不要用FileInputStream,Web工程不好找路径。用类加载器。
- getBean()返回的是Object类型
把两处使用new创建对象的改为用反射创建
Client中
1 | // IAccountService as = new AccountServiceImpl(); |
AccountServiceImpl中
1 | // private IAccountDao accountDao = new AccountDaoImpl(); |
Object类强转为对应的类
改进后程序的UML类图长这样
此时如果把AccountServiceImpl删了,程序可以运行,抛ClassNotFoundException
工厂模式的问题与改进
如果要在Client中多次调用Service?
1 | for(int i=0;i<5;i++) { |
并在AccountServiceImpl中加一个成员变量i
1 | public class AccountServiceImpl implements IAccountService { |
运行
com.itheima.service.impl.AccountServiceImpl@610455d6
保存了账户
1
com.itheima.service.impl.AccountServiceImpl@511d50c0
保存了账户
1
com.itheima.service.impl.AccountServiceImpl@60e53b93
保存了账户
1
com.itheima.service.impl.AccountServiceImpl@5e2de80c
保存了账户
1
com.itheima.service.impl.AccountServiceImpl@1d44bcfa
保存了账户
1Process finished with exit code 0
可以看到,AccountServiceImpl创建了5次,每次都是一个新的对象
此时的对象是多例,效率没有单例高
可以在BeanFactory中把创建出的对象都存起来
1 | //定义一个Map,用于存放我们要创建的对象。我们把它称之为容器 |
在静态代码块中,得到配置文件的输入流后,实例化这个容器。取出配置文件中所有的key-value,创建并保存它们
1 | // 实例化容器 |
获取Beans,不用newInstance(),直接从容器中取即可
1 | public static Object getBean(String beanName){ |
这样修改后,调用5次Service打印的结果是
com.itheima.service.impl.AccountServiceImpl@610455d6
保存了账户
1
com.itheima.service.impl.AccountServiceImpl@610455d6
保存了账户
2
com.itheima.service.impl.AccountServiceImpl@610455d6
保存了账户
3
com.itheima.service.impl.AccountServiceImpl@610455d6
保存了账户
4
com.itheima.service.impl.AccountServiceImpl@610455d6
保存了账户
5Process finished with exit code 0
此时的AccountServiceImpl就是单例的了
但有一个问题是,这个i在多线程时是不安全的
应该把它移到方法里面,就没有这个问题了(实际使用一般也是这样)
1 | // private int i = 1; |
工厂模式使用套路总结
1.创建BeanFactory
1 | // BeanFactory.java |
2.配置文件
1 | beans.properties |
3.获取Bean
1 | // Client.java |