工厂模式 + 单例模式实战

一、绪论

1、Tips

这篇文章是基于上篇文章 《工厂模式为 MVC 解耦》之上的后续,建议先看上篇文章。

2、提出问题

上篇文章使用工厂模式和反射为保存账户的功能进行解耦,可以决解缺少某个类时编译不出错,但是运行抛异常,从而降低耦合。

但是工厂模式还是有一定的问题的,我们先来看下在 AccountDemo 中,连续创建五次的 AccountServiceImpl 的对象内存地址分别是什么。

  • 修改下 AccountDemo 中的代码,其他不变,具体如下:
public class AccountDemo {

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
            System.out.println(as);
        }
//            as.saveAccount();
    }
}

//*************************************************
// 运行结果
wiki.laona.service.impl.AccountServiceImpl@1540e19d
wiki.laona.service.impl.AccountServiceImpl@677327b6
wiki.laona.service.impl.AccountServiceImpl@14ae5a5
wiki.laona.service.impl.AccountServiceImpl@7f31245a
wiki.laona.service.impl.AccountServiceImpl@6d6f6e28
  • 从运行结果可以明显看出来,五个对象的内存地址都是不一样的,那就意味着每次调用 BeanFactory.getBean 方法都会创建一个新的对象。这样耦合是解决了但是内存开销也随之变大了。

3、刨析一下

原因其实这也不能理解,在 getBean 方法中,每次获取 bean 的对象实例都是通过反射 newInstance 新建一个实例,所以每次调用都会在内存中 new 一个新的对象,导致了内存消耗,这就是多例的存在的问题。解决这个问题,我们可以通过单例的思想解决。有关单例模式的内容可以查看博客《Java 设计模式 -- 单例模式》, 另外 getBeam() 方法如下:

 /**
  * 通过资源名获取类对象
  *
  * @param beanName 资源名
  * @return {@link Object} 类对象
  * @throws ClassNotFoundException {@link ClassNotFoundException} 未找到类对象异常
  */
public static Object getBean(String beanName) {
    Object bean = null;
    String res = props.getProperty(beanName);
    try {
        bean = Class.forName(res).newInstance();
    } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
        e.printStackTrace();
    }
    return bean;
}

二、工厂模式结合单例模式

改造工厂模式,我们只需要改动 BeanFactory 类就行了。

1、主要思路

  • 通过 Map 保存 AccountService 和 AccountDao 的实例对象
  • 在 getBean 中返回 Map 中保存的AccountService 和 AccountDao 的实例对象。

2、 代码实现

2.1、建立保存

  • 代码实现:
/**
 * 保存 AccountService 和 AccountDao 的实例对象的 Map 
 */
private static Map<String, Object> beans;

2.2、把实例对象保存到 Map 中

  • 这一部分在静态代码块中实现,代码如下:
static {
    try {
        props = new Properties();
        InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        props.load(is);
        // 初始化 Map
        beans = new HashMap<>();
        // 获取 prop 中的实例对象实例
        Enumeration<Object> keys = props.keys();
        while (keys.hasMoreElements()) {
            // 获取 key
            String key = keys.nextElement().toString();
            // 获取 properties 文件中的实例路径
            String beanPath = props.getProperty(key);
            // 反射获取实例对象
            Object val = Class.forName(key).newInstance();
            // 保存 key 和 value 到 beans 中
            beans.put(key, val);
        }
    } catch (Exception e) {
        throw new ExceptionInInitializerError("初始化 Properties 文件失败~!");
    }
}

2.3、在 getBean 方法中返回对象实例

  • 通过 key 获取 Map 的对象实例,返回便可。
  • 代码实现如下:
/**
 * 通过名字获取实例对象
 * @param beanPath 实例名称
 * @return {@link Object} 实例对象
 */
public static Object getBean(String beanPath) {
    return beans.get(beanPath);
}

3、测试

3.1、集成测试

  • 因为直接重写了 getBean 方法,所以在 AccountDemo 中无需修改,直接运行,此时获取的内存地址是一样,代码如下:
public class AccountDemo {

    public static void main(String[] args) {
        IAccountService as = null;
        for (int i = 0; i < 5; i++) {
            as = (IAccountService) BeanFactory.getBean("accountService");
            System.out.println(as);
        }
        as.saveAccount();
    }
}

//*********************************
// 运行结果
wiki.laona.service.impl.AccountServiceImpl@1540e19d
wiki.laona.service.impl.AccountServiceImpl@1540e19d
wiki.laona.service.impl.AccountServiceImpl@1540e19d
wiki.laona.service.impl.AccountServiceImpl@1540e19d
wiki.laona.service.impl.AccountServiceImpl@1540e19d
已保存账户~!

三、小结

工厂模式基础上再结合单例模式的编程思想,不但可以降低耦合,同时也可以节省内存消耗,为程序提供更高的鲁棒性。


人若无名,专心练剑~!

猜你喜欢

转载自www.cnblogs.com/huaiangg/p/12444249.html