Spring笔记3 工厂模式

https://www.bilibili.com/video/av47952931
p9~14


程序的耦合与解耦 (以jdbc注册驱动为例)

jdbc操作中,注册数据库驱动时,有2种方法

// 方法1
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
// 方法2
Class.forName("com.mysql.jdbc.Driver");

在pom.xml中添加了依赖的情况下都可以正常运行

   <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
    </dependencies>

但是,如果去掉这段依赖
方法1报Error,无法通过编译

Error:(26, 56) java: 程序包com.mysql.jdbc不存在

而方法2报Exception,可以通过编译(无法运行)

Exception in thread "main" java.lang.ClassNotFoundException: com.mysql.jdbc.Driver

因为方法1依赖一个具体的驱动类,而方法2用反射,依赖的只是一个字符串
但是这个字符串仍是写死在代码里的
应该写到配置文件里去,进一步减少耦合

实际开发中应该做到:
编译期不依赖,运行时才依赖
解耦的思路:
第一步:使用反射来创建对象,而避免使用new关键字
第二步:通过读取配置文件来获取要创建的对象全限定类名


工厂模式

原始的分层实现方法

  • Client : 模拟一个表现层,用于调用业务层
  • IAccountService : 账户业务层的接口
  • AccountServiceImpl : 账户的业务层实现类
  • IAccountDao : 账户的持久层接口
  • AccountDaoImpl : 账户的持久层实现类

其中,有两个依赖关系

  1. 表现层调用业务层时
public class Client {
    public static void main(String[] args) {
        IAccountService as = new AccountServiceImpl();
        as.saveAccount();
    }
}
  1. 业务层调用持久层时
public class AccountServiceImpl implements IAccountService {
	private IAccountDao accountDao = new AccountDaoImpl();
	public void  saveAccount(){
        accountDao.saveAccount();
    }
}

都用到了new,耦合度高
如果此时把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)

accountService = com.itheima.service.impl.AccountServiceImpl
accountDao = com.itheima.dao.impl.AccountDaoImpl

创建BeanFactory类读取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){
            // 抛一个Error,没有获取配置信息后面想都不要想
            throw new ExceptionInInitializerError("初始化properties失败!");
        }
    }

	// 根据Bean的名称获取bean对象
    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;
    }
}

注意:

  1. 读配置文件时不要用FileInputStream,Web工程不好找路径。用类加载器。
  2. getBean()返回的是Object类型

把两处使用new创建对象的改为用反射创建
Client中

// IAccountService as = new AccountServiceImpl();
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");

AccountServiceImpl中

// private IAccountDao accountDao = new AccountDaoImpl();
private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");

Object类强转为对应的类

改进后程序的UML类图长这样

此时如果把AccountServiceImpl删了,程序可以运行,抛ClassNotFoundException

工厂模式的问题与改进

如果要在Client中多次调用Service?

for(int i=0;i<5;i++) {
	IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
	System.out.println(as);
	as.saveAccount();
}

并在AccountServiceImpl中加一个成员变量i

public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
    private int i = 1;
    public void  saveAccount(){
        accountDao.saveAccount();
        System.out.println(i);
        i++;
    }
}

运行

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
保存了账户
1

Process finished with exit code 0

可以看到,AccountServiceImpl创建了5次,每次都是一个新的对象
此时的对象是多例,效率没有单例高

可以在BeanFactory中把创建出的对象都存起来

//定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
private static Map<String,Object> beans;

在静态代码块中,得到配置文件的输入流后,实例化这个容器。取出配置文件中所有的key-value,创建并保存它们

// 实例化容器
beans = new HashMap<String,Object>();
// 取出配置文件中所有的Key
Enumeration keys = props.keys();
// 遍历枚举
while (keys.hasMoreElements()){
    // 取出每个Key
    String key = keys.nextElement().toString();
    // 根据key获取value
    String beanPath = props.getProperty(key);
    // 反射创建对象
    Object value = Class.forName(beanPath).newInstance();
    // 把key和value存入容器中
    beans.put(key,value);
}

获取Beans,不用newInstance(),直接从容器中取即可

public static Object getBean(String beanName){
	return beans.get(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
保存了账户
5

Process finished with exit code 0

此时的AccountServiceImpl就是单例的了

但有一个问题是,这个i在多线程时是不安全的
应该把它移到方法里面,就没有这个问题了(实际使用一般也是这样)

//    private int i = 1;

    public void  saveAccount(){
        int i = 1;
        accountDao.saveAccount();
        System.out.println(i);
        i++;
    }

工厂模式使用套路总结

  1. 创建BeanFactory
// BeanFactory.java

public class BeanFactory {
    //定义一个Properties对象
    private static Properties props;

    //定义一个Map,用于存放我们要创建的对象。称之为容器
    private static Map<String,Object> beans;

    //使用静态代码块为Properties对象赋值
    static {
        try {
            // 1.实例化对象
            props = new Properties();
            // 2.获取properties文件的流对象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            props.load(in);
            // 3.实例化容器
            beans = new HashMap<String,Object>();
            // 4.取出配置文件中所有的Key
            Enumeration keys = props.keys();
            // 5.遍历枚举
            while (keys.hasMoreElements()){
                // 取出每个Key
                String key = keys.nextElement().toString();
                // 根据key获取value
                String beanPath = props.getProperty(key);
                // 反射创建对象
                Object value = Class.forName(beanPath).newInstance();
                // 把key和value存入容器中
                beans.put(key,value);
            }
        }catch(Exception e){
            throw new ExceptionInInitializerError("初始化properties失败!");
        }
    }

    // 根据bean的名称获取对象
    public static Object getBean(String beanName){
        return beans.get(beanName);
    }
}
  1. 配置文件
// beans.properties

accountService=com.itheima.service.impl.AccountServiceImpl
accountDao=com.itheima.dao.impl.AccountDaoImpl
  1. 获取Bean
// Client.java

public class Client {

    public static void main(String[] args) {
    	IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
    	as.saveAccount();
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值