上篇博客JDBC的demo展示了程序之间的耦合(程序间的依赖性),那么如何进行解耦,就使用到工厂模式。
在开发过程中,项目主要分为业务层、持久层(dao,管数据访问的)和表现层。
Maven工程架构如下:
└── com
└── eastnotes
├── dao
│ ├── AccountDao.java
│ └── impl
│ └── AccountDaoImpl.java
├── service
│ ├── AccountService.java
│ └── impl
│ └── AccountServiceImpl.java
└── ui
└── Client.java
dao层
/**
* 账户的持久层操作类
*/
public class AccountDaoImpl implements AccountDao {
public void saveAccount(){
System.out.println("保存了账户");
}
}
public interface IAccountDao {
void saveAccount();
}
service层
service层负责业务逻辑,向dao层发送数据访问指令。
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = new AccountDaoImpl();
public void saveAccount(){
accountDao.saveAccount();
}
}
public interface IAccountService {
//模拟保存账户
void saveAccount();
}
ui是表现层,用于调用业务层方法。
/**
* 模拟一个表现层,用于调业务层
*/
public class Client {
public static void main(String[] args) {
AccountService as = new AccountServiceImpl();
as.saveAccount();
}
}
最终得到dao层方法的输出:
保存了账户
Client类使用new关键字创建了一个业务层的实现类对象,在业务层实现类,AccountServiceImpl同样使用new关键字创建了一个持久层实现
AccountService as = new AccountServiceImpl();
private AccountDao accountDao = new AccountDaoImpl();
那么使用工厂模式解耦:
新建factory包,beanfactory类:用于解耦的工厂类。作用是创建Bean对象。
在这些组成部分中,有的类可以被重复使用,比如说service可以被很多的serverlet使用,dao可以被很多的service来使用。那么这些被重复使用的类就可以成为可重用组件,也就是Bean。所以,我们新创建的这个factory工厂类,就是来创建service和dao对象的。
解耦的思路分为两步:
- 需要一个配置文件来配置我们的service和dao,配置文件的内容是一个个的键值对,键是唯一表示,值是这个类的全限定类名:唯一标志=全限定类名。
- 读取配置文件,通过反射创建对象。配置文件可选xml或者是properties,在此处我们使用properties。
配置文件放在resources目录下,取名为bean.properties,文件内容如下:
accountService=com.eastnotes.service.impl.AccountServiceImpl
accountDao=com.eastnotes.dao.impl.AccountDaoImpl
├── java
│ └── com
│ └── eastnotes
│ ├── dao
│ │ ├── AccountDao.java
│ │ └── impl
│ │ └── AccountDaoImpl.java
│ ├── factory
│ │ └── BeanFactory.java
│ ├── service
│ │ ├── AccountService.java
│ │ └── impl
│ │ └── AccountServiceImpl.java
│ └── ui
│ └── Client.java
└── resources
└── bean.properties
package com.eastnotes.factory;
import java.io.InputStream;
import java.util.Properties;
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//使用静态代码块为Properties对象赋值
static{
try {
//实例化对象
props = new Properties();
//获取Properties文件流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
}catch (Exception e){
throw new ExceptionInInitializerError("初始化Properties失败");
}
}
/**
* 根据bean的名称获取bean对象
* 使用static关键字可以通过类名来访问这个函数
* @param beanname
* @return
*/
public static Object getBean(String beanname){
Object bean = null;
try {
String beanPath = props.getProperty(beanname);
bean = Class.forName(beanPath).newInstance(); //反射创建对象
}catch (Exception e){
e.printStackTrace();
}
return bean;
}
}
写完这个可以提供Bean对象的工厂类之后,我们就可以改造业务层和表现层的对象创建方式了:
将业务层创建对象的方式改为:
private AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao");
将表现层创建对象的方式改为:
AccountService as = (AccountService) BeanFactory.getBean("accountService");
工厂解耦问题
我们将表现层的代码改为下面:
public class Client {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
AccountService as = (AccountService) BeanFactory.getBean("accountService");
System.out.println(as);
}
}
}
输出结果:
com.itheima.service.impl.AccountServiceImpl@4f023edb
com.itheima.service.impl.AccountServiceImpl@3a71f4dd
com.itheima.service.impl.AccountServiceImpl@7adf9f5f
com.itheima.service.impl.AccountServiceImpl@85ede7b
com.itheima.service.impl.AccountServiceImpl@5674cd4d
由此我们可以看出,通过工厂类得到的对象是多例的,因为每次生成的对象都不一样。单例对象从始至终都是一个对象,我们的Servlet就是一个单例对象。单例对象的类成员只会被初始化一次,而多例对象会被初始化多次。
因此多例对象的执行效率没有单例对象高。而单例对象存在线程安全问题。但是,在我们的Service和DAO中,我们并没有在类中定义成员变量,因此不存在线程问题,所以我们并不需要多例对象。
出现多例对象的问题出现在工厂类中的下面这个代码中:
bean = Class.forName(beanPath).newInstance();
我们通过反射使用newInstance创建对象的时候,每次都会调用默认构造函数。因此,当他创建完对象之后,我们应该马上把对象存起来。用什么存呢?Map容器
工厂解耦改进
首先,在工厂类中定义一个由static修饰的Map,用于存放我们要创建的对象,我们将它称之为容器。这个Map的key是String类型,value是Object类型。
然后,再静态代码块中,取出配置文件中的所有key,然后根据每个key获取value,也就是我们的全限定类名,然后通过反射创建每一个对象。此时key有了,value有了,我们就可以把它存入Map(容器)中。
静态代码块只在类加载的时候执行一次,等到它执行完,这个容器内已经装好了我们所有需要的Bean对象,那么当我们获取对象的时候就没有这么麻烦了。直接根据bean的名称从容器中取出对象,然后直接返回就好了。
package com.eastnotes.factory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class BeanFactoryUpdate {
//定义一个Properties对象
private static Properties propres;
//定义一个Map,用于存放我们要创建的对象,我们把它称之为容器,创建它是为了实现单例对象
public static Map<String,Object> beans;
//使用静态代码块为Properties对象赋值,在类加载的时候只执行一次
static {
try {
//实例化对象
propres = new Properties();
//获取Properties文件的流对象
InputStream in = BeanFactoryUpdate.class.getClassLoader().getResourceAsStream("bean.properties");
propres.load(in);
//实例化容器对象
beans = new HashMap<String, Object>();
//取出配置文件中所有的keys
Enumeration keys = propres.keys();
//遍历枚举
while (keys.hasMoreElements()){
//取出每个key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = propres.getProperty(key);
//反射创建对象
try {
Object value = Class.forName(beanPath).newInstance();
//把key和value存入map中
beans.put(key,value);
}catch (Exception e){
e.printStackTrace();
}
}
} catch (IOException e) {
throw new ExceptionInInitializerError("初始化Properties失败");
}
}
//【单例模式下】根据bean的名称获取bean对象
public static Object getBean(String beanName){
return beans.get(beanName);
}
}
这样改进后,此工厂在创建Bean对象的时候会使用单例的形式,当我们在表现层循环获取某个对象的时候,得到的将是同一个对象。