文章目录
一、环境准备
本文想以jdbc的开发代码为例子来说明程序间的耦合问题,所以事先建立起数据库以及对于的maven工程
1.数据库环境
1.1创建数据表
create table account(
id int primary key auto_increment,
name varchar(40),
money float
)character set utf8 collate utf8_general_ci;
insert into account(name,money) values('aaa',1000);
insert into account(name,money) values('bbb',1000);
insert into account(name,money) values('ccc',1000);
2.maven工程
就是新建一个普通的maven工程即可
2.1导入mysql的jar包坐标
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>
2.2新建java类
新建com.xpt.jdbc.JdbcDemo01
public class JdbcDemo1 {
public static void main(String[] args) throws Exception {
//1.注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/eesy", "root", "admin");
//3.获取数据库操作预处理对象
PreparedStatement ps = connection.prepareStatement("select * from account");
//4.执行sql,获得结果集
ResultSet rs = ps.executeQuery();
//5.遍历结果集
while (rs.next()){
System.out.println(rs.getString("name"));
}
//6.释放资源
rs.close();
ps.close();
connection.close();
}
}
2.3执行上述代码
- 结果:
二、编译期依赖
1注释掉上述代码中mysql对应的jar包
- 注释
- 程序提示错误
- 运行报错
三、耦合
-
耦合:程序间的依赖关系
-
包括:
- 类之间的依赖
- 方法之间的依赖
-
解耦:
- 降低程序间的依赖关系(只能降低,不能避免)
-
解决思路:
- 实际开发中,要做到:编译期不依赖,运行期才依赖
- 对于上述例子,我们可以在注册驱动时采用
Class.forName()
- 显然上述例子依然无法运行,但这个时候只会发生运行期异常了
- 对于上述例子,我们可以在注册驱动时采用
- 上述的例子带给我们的一个解决思路就是:
- 使用反射来创建对象,避免使用new
- 继续思考,这里还有一个问题在于,上述是通过一个字符串来指定具体的类,如果在后续的开发中需要更换那个类,我们还是需要修改那个字符串,所以进一步的:
- 通过配置文件的方式来得到要创建对象的全限定类名
- 实际开发中,要做到:编译期不依赖,运行期才依赖
四、曾经代码中的问题分析
1.曾经的业务层和持久层代码
业务层调用持久层的dao类来实现对应实体类的CRUD操作
- 上述的代码中存在的问题,正是我们前面提到的需要避免的一个问题。
- 同样的,在以前的开发习惯中,会发现有大量的通过
new
关键字来创建对象的代码,这正是我们目前急需避免的
五、工厂模式解耦
1.新建配置文件
- bean.properties
- 通过键值对来指定唯一的全限定类名
accountService=com.xpt.service.impl.AccountServiceImpl
accountDao=com.xpt.dao.impl.AccountDaoImpl
2.新建工厂类
- 说明
- 代码
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//使用静态代码块为props对象赋值
static {
try {
//实例化对象
props = new Properties();
//获取Properties文件的流对象
//注意这里的获取方式 一定不要直接写url目录,因为需要考虑到项目编译,发布后资源路径改变带来的问题
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
} catch (Exception e) {
throw new ExceptionInInitializerError("初始化properties失败");
}
}
//注意返回值 和参数
public static Object getBean(String beanName){
Object bean = null;
String beanPath = props.getProperty(beanName);
try {
bean = Class.forName(beanPath).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return bean;
}
}
3.使用工厂类重新获取对象
public class Client {
public static void main(String[] args) {
// IAccountService as = new AccountServiceImpl();
//使用工厂类进行解耦
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
as.saveAccount();
}
}
3.2业务层
public class AccountServiceImpl implements IAccountService {
// IAccountDao accountDao = new AccountDaoImpl();
IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
public void saveAccount() {
accountDao.saveAccount();
}
}
- 通过观察运行结果,利用上述工厂类可以完成解耦
4.上述代码的问题
4.1打印通过工厂得到的对象
- 代码
public class Client {
public static void main(String[] args) {
// IAccountService as = new AccountServiceImpl();
//使用工厂类进行解耦
for (int i = 0; i < 5; i++) {
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
System.out.println(as);
}
// as.saveAccount();
}
}
- 结果
4.2分析
- 上述的方式得到的对象是多例的,而学习过Servlet知道,它是单例的,多例在效率上不及单例。
- 原因:
- 解决思路:由于每一次调用都会获取一个新的实例,当该程序被执行完毕后就被垃圾回收机制回收了。这里要怎么解决呢?如果我们可以每次获取一个新实例后将它存储起来,使用完了归还回去,这样就能解决这个问题。那具体用什么来存储呢?
- 这里我们往往使用一个map来存储,并把它定义为容器。
5.工厂模式解耦升级
将其改造为单例的
5.1定义map存储实例对象
- 代码
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//定义容器 存储
private static Map<String,Object> beans;
//使用静态代码块为props对象赋值
static {
try {
//实例化对象
props = new Properties();
//获取Properties文件的流对象
//注意这里的获取方式 一定不要直接写url目录,因为需要考虑到项目编译,发布后资源路径改变带来的问题
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//实例化容器
beans = new HashMap<String, Object>();
//获取map所有的key
Enumeration keys = props.keys();
while (keys.hasMoreElements()){
//取出每个Key
String key = keys.nextElement().toString();
//根据Key 获取value
String beanPath = props.getProperty(key);
//根据beanPath 反射创建对象
Object value = Class.forName(beanPath).newInstance();
//存入map
beans.put(key, value);
}
} catch (Exception e) {
throw new ExceptionInInitializerError("初始化properties失败");
}
}
//根据bean的名称获取单例对象
public static Object getBean(String beanName){
return beans.get(beanName);
}
}
- 结果
- 对象是在静态代码块中被创建的,然后保存在了map中,后面的
getBean
方法是从这里获取的,所以这里每一次获取的对象都是通过map来获取的,并没有创建新的对象,所以这里最后的效果就算单例的。