Spring | 工厂模式

Spring | 工厂模式

什么是 Spring

在 Spring 诞生以前,企业级 JavaWeb 项目多使用 EJB(Enterprise Java Bean)进行开发,EJB 运行环境苛刻,代码可移植性差。

Spring 是一个轻量级的 JavaEE 解决方案,整合了众多优秀的设计模式。

  • 轻量级,是指 Spring 对于运行环境没有额外要求,代码移植性高,可以运行在各种开源(Tomacat、Jetty等)或收费(Weblogic等)的 Web 服务器上,不需要实现额外接口
  • JavaEE 解决方案是指 Spring 框架可以作用于 Web 项目三层架构的任意一层(Controller、Service、DAO),而像 Struts2 或者 MyBatis 只是作用于某一层的框架
  • Spring 中使用了工厂、代理、模板、策略等众多设计模式

工厂设计模式

设计模式

广义上,设计模式就是在面向对象设计中,一些用于解决特定问题的经典的模板式代码,狭义上,设计模式专指 GOF 四人组(Design Patterns: Elements of Reusable Object-Oriented Software 一书的四位作者)定义的 23 种具体的设计模式,例如工厂、适配器、装饰器、门面、代理、模板等

工厂模式基本概念

如果不使用工厂,一般我们使用 new 关键字创建对象,例如在 Controller 层创建 UserService 接口的对象,在 Service 层创建 UserDAO 接口的对象

UserService userService = new UserServiceImpl();
UserDAO userDAO = new UserDAOImpl();

这样把接口的实现类的对象,直接硬编码在程序中,虽然编写简单,但是十分不利于维护,例如在后期项目维护中,编写了一个 UserService 的新的实现类 UserServiceImpl2,使用此新的实现类需要修改代码,如果程序中有多处使用,维护上会非常麻烦,总结就是,代码之间的耦合性很强,属于强关联关系,一方的改变会影响另一方,所以我们要实现代码的 解耦合

简单工厂设计

我们考虑设计这样一个简单的工厂,它通过外部配置文件读取接口与其实现类的映射关系,使用反射为使用者创建对象

package cool.yzt.factory;

import cool.yzt.dao.UserDAO;
import cool.yzt.service.UserService;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * @Author: yzt
 * @Description: 创建对象的工厂类
 */
public class BeanFactory {
    // 保存接口与实现类的映射关系
    private static Properties pros = new Properties();

    // 从配置文件中读取映射关系
    static {
        try {
            InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("applicationContext.properties");
            pros.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 创建 UserService 对象的静态方法
    public static UserService getUserService() {
        UserService userService = null;
        try {
            // 通过反射的方法创建对象,解耦合
            Class clazz = Class.forName(pros.getProperty("UserService"));
            userService = (UserService) clazz.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return userService;
    }

    // 创建 UserDAO 对象的静态方法
    public static UserDAO getUserDAO() {
        UserDAO userDAO = null;

        try {
            Class clazz = Class.forName(pros.getProperty("UserDAO"));
            userDAO = (UserDAO) clazz.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return userDAO;
    }
}

配置文件 applicationContext.properties 如下,注意实现类要写全类名

UserService = cool.yzt.service.UserServiceImpl
UserDAO = cool.yzt.dao.UserDAOImpl

使用工厂类获取实现类对象

UserService userService = BeanFactory.getUserService();
UserDAO userDAO = BeanFactory.getUserDAO();

可以看到,通过一个简单工厂我们就可以解决代码之间的耦合问题,创建对象时并不会出现 new 关键字,但是又会有新的问题出现:工厂类中获取对象的方法存在大量的代码冗余,且每一个类都要编写一个单独的方法以获取对象。

通用工厂设计

在工厂类中添加一个 getBean(String key) 方法,可以根据传入的参数获取不同的类的对象

public static Object getBean(String key) {
    Object obj = null;
    try {
        Class clazz = Class.forName(pros.getProperty(key));
        obj = clazz.newInstance();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return obj;
}

使用通用工厂创建对象,需要注意,工厂类返回的是一个 Object 对象,需要进行类型强制转换

UserService userService = (UserService) BeanFactory.getBean("UserService");
UserDAO userDAO = (UserDAO) BeanFactory.getBean("UserDAO");

总结

使用通用工厂获得接口的实现类对象,需要在配置文件中填写对应 keyvalue,也就是接口与实现类的全类名之间的映射,然后就可以传入 key 作为参数通过工厂获取对应的对象,实际上,Spring 框架就可以看做一个工厂,通其编写 Spring 的配置文件 applicationContext.xml 和工厂类 ApplicationContext 就可以实现控制反转与依赖注入,降低了代码之间的耦合性,且无需我们自己创建工厂。

参考

孙哥说Spring5

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://yzt.cool/archives/2020-06-19-spring-factory