设计模式之工厂模式

1039

工厂模式

什么是工厂模式

工厂模式是对构造对象,实例化,初始化过程的一种封装。它帮我们解决new对象的问题,假如我们需要创建一个Person对象, 我们常用的方式是Person person = new Person();但是这样会导致把这个对象诞生的过程死死的捆绑在我们的代码当中,宿主类与实例化过程强耦合在一起。

我们看这样一个例子:

现有一个StudentDao接口

public interface StudentDao {
	void add(Student stu);
	void update(Student stu);
}

以及一个实现类StudentDaoImpl

public class StudentImpl implements StudentDao{

	@Override
	public void add(Student stu) {
		System.out.println("StudentImpl.add()...");
	}

	@Override
	public void update(Student stu) {
		System.out.println("StudentImpl.update().....");
	}
	
}

如果我们想使用StudentDao的功能,那么我们会创建一个实例:

StudentDao studentDao = new StudentDaoImpl();
// stu创建省略
studentDao.add(stu);

这样我们可能会在同一个宿主类当中创建多个这样的实例,就导致创建过程死死的耦合在一起。我们的代码中可能会有很多这样的实例。假如有一天,你的上司来和你说StudentDaoImpl的实现逻辑想要变一下,你的做法可能是重新写一遍StudentDaoImpl,那么如果等你花了很久把这个类的实现逻辑重新改完了,你的上司又和你说这样不太好还是改回来吧,那你是不是想杀了他的冲动都有(/斜眼笑)。

这只是一种情况,大量的实例创建耦合在代码当中导致代码非常的笨重,对后期的维护和扩展都会带来很多麻烦。其实我们并不关心到底使用的是哪个对象更不关心它是怎么产生出来的,我们只需要一个接口类型的对象比如studentDao

对象是对现实世界的一种抽象,所以为了解决这种问题我们可以放眼与现实世界。如果我们要用电脑,我们不需要自己制造一个电脑,而是去买一台,他们在工厂中产生,至于电脑是怎么制造的,内部结构是什么我们并不需要知道,只要是一个电脑就行。同样写代码亦如此,将具体实现放到工厂中制造,只要最后一个StudentDao或者其他的什么鬼就可以,于是就这样工厂模式就此诞生了。

如何实现工厂模式

还是上面的例子,StudentDao

  1. 创建StudentDao接口
public interface StudentDao {
    void add(String name);
    void update(String name);
}
  1. 创建StudentDao接口的实现类
public class StudentDaoImpl implements StudentDao{
    @Override
    public void add(String name) {
        System.out.println(name+"学生被StudentDaoImpl的add方法添加到数据库....");
    }

    @Override
    public void update(String name) {
        System.out.println(name+"学生被StudentDaoImpl的update方法修改了信息");
    }
}
  1. 写一个beans.properties配置文件,里面包含着具体创建哪一个实现类
xyz.guqing.dao.StudentDao=xyz.guqing.dao.StudentDaoImpl
  1. 首先我们需要造一个工厂,BeanFactory用于生产对象
public class BeanFactory {
    private static Properties props = null;
    private static InputStream in = null;
    static {
        try{
            //加载配置文件内容到流中
            in = BeanFactory.class.getClassLoader()
                    .getResourceAsStream("beans.properties");

            props = new Properties();

            //加载配置文件到Properties中
            props.load(in);
        }catch(Exception e){
            throw new RuntimeException(e);
        }finally{
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // StudentDao实例的获取方法
    public static StudentDao getStudentDao(){
        try{
            //得到具体实现类的名称
            String daoClassName = props.getProperty("xyz.guqing.dao.StudentDao");
            // 通过Class.forName根据名称将这个类加载到JVM
            Class clazz = Class.forName(daoClassName);
            // 创建实例对象
            return (StudentDao) clazz.newInstance();
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }
}
  1. 根据工厂获取实例对象
public class BeanFactoryTest {
    public static void main(String[] args) {
        StudentDao studentDao = BeanFactory.getStudentDao();
        studentDao.add("张三");
    }
}

输出结果:

张三学生被StudentDaoImpl的add方法添加到数据库....

假设现在需要更改StudentDaoImpl的实现逻辑,那么我们不需要去改它,重新创建一个StudentDaoImpl2

public class StudentDaoImpl2 implements StudentDao{
    @Override
    public void add(String name) {
        System.out.println("StudentDaoImpl2的 add 方法" + name + "学生被调用....");
    }

    @Override
    public void update(String name) {
        System.out.println("StudentDaoImpl2的 update 方法" + name + "学生被调用....");
    }
}

然后在beans.properties的配置文件中更换实现类为StudentDaoImpl2

xyz.guqing.dao.StudentDao=xyz.guqing.dao.StudentDaoImpl2

这样就可以了,其他的我们什么都不需要改变,再次执行测试类,可以看到

StudentDaoImpl2的 add 方法张三学生被调用....

这就是工厂模式。

甚至还可以对以上内容稍作修改

public class BeanFactory {
    private static Map<String, Object> beansMap = new HashMap<>();

    private static Map<String, String> readBeanAttribute() {
        Map<String, String> beanAttributeMap = new HashMap<>();
        try {
            // bean配置文件
            InputStream in = BeanFactory1.class.getClassLoader()
                    .getResourceAsStream("beans.xml");

            if (in == null) {
                throw new RuntimeException("  Error : Config file beans.xml does not exist!");
            }
            // 解析配置文件
            SAXReader reader = new SAXReader();
            Document document = reader.read(in);
            //获取根元素
            Element root = document.getRootElement();
            Iterator it =  root.elementIterator();
            while (it.hasNext()) {
                Element childrenElement = (Element)it.next();
                String id = childrenElement.attribute("id").getValue();
                String className = childrenElement.attribute("className").getValue();

                beanAttributeMap.put(id, className);
            }

            return beanAttributeMap;
        } catch (Exception ex) {
            throw new RuntimeException("Error parsing configuration file." + ex.getMessage());
        }
    }

    public static Object getBean(String beanName) {
        Object bean = beansMap.get(beanName);
        if(bean != null) {
            return bean;
        }

        Map<String, String> beanAttribute = readBeanAttribute();
        String className = beanAttribute.get(beanName);
        // 通过Class.forName根据名称将这个类加载到JVM
        Class clazz = null;
        try {
            clazz = Class.forName(className);
            Object object = clazz.newInstance();
            // 将实例放入map
            beansMap.put(beanName, object);

            return object;
        } catch (Exception e) {
            throw new RuntimeException("Cannot create instance."+e.getMessage());
        }
    }
}

然后写一个beans.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="stuDao" className="xyz.guqing.dao.StudentDaoImpl2">
    </bean>
</beans>

使用:

public class BeanFactoryTest {
    public static void main(String[] args) {
        StudentDao studentDao = (StudentDao)getBean("stuDao");
        studentDao.add("张三");
    }
}

如此比之前就更加通用了