设计模式之简单工厂模式
简单工厂模式就是将多个类对象交给工厂类来生成的设计方式
在不使用工厂模式前我们一般在某个类A中需要使用到类B的方法,那么我们首先想到的就是将类B在类A中进行实例化即B 实例 = new B();这样的方式对于我们初学者或者是小型的项目应该是不会构成威胁的,但是如果这个是一个应用于大型的项目,用户的需求也是经常需要改变的,如果我们使用这种方式硬编码耦合的方式来进行编码,那么如果需求驱使我们必须将B类换成C类,那么我们就只能去A类中进行更改;但在一个大型项目可能用到类B的可能有成百上千个,如果我们这样求修改的话那是不可想象的,根本是没法修改的;
简单工厂模式就可以为我们解决上面那个难题,设计如下:首先我们需要转换的是由原先的面向实现类编程转为面向接口编程,对于A对象而言,它只是需要调用B对象中的方法,而并不关心B对象的实现过程,转换使用面向接口编程就是让B实现类继承自IB接口,而A类只需要与IB接口耦合,而与其实现类进行解耦,但这里说的耦合也并不是说是在A类中用New来实例化接口,这样就没有实际的意义了,而是定义一个工厂类IBFactory,由该工厂类来创建IB的实例,然后类A通过调用IBFactory中的方法来得到IB实例;
下面模拟使用简单工厂模式实现的需求:
需求:假设现在的我们需要对数据库中的内容进行输出,需要依赖于一个输出的类,但现在有两种方式进行输出,分别是:excel与word;(这里我简单模拟,因此代码会很简单);
1.首先我们先用写一个输出方式的接口,代码如下:
package xidian.sl.interfaces; public interface Print { public void outPrint(); }
2.然后需要写出它的两个实现类,如下:
excel方式:
package xidian.sl.impl; import xidian.sl.interfaces.Print; public class ExcelImpl implements Print { @Override public void outPrint() { System.out.println("使用excel进行导出"); } }
word方式:
package xidian.sl.impl; import xidian.sl.interfaces.Print; public class WordImpl implements Print { @Override public void outPrint() { System.out.println("使用word进行导出"); } }
3.然后实现一个工厂类:
package xidian.sl.interfaces; import xidian.sl.impl.ExcelImpl; public class PrintFactory { /** * 用于获取print实例的方法 * */ public Print getPrint(){ /** * 这里默认返回的是excel方式进行导出 * */ return new ExcelImpl(); } }
4.进行简单工厂方式的实现:
package xidian.sl.impl; import xidian.sl.interfaces.Print; import xidian.sl.interfaces.PrintFactory; public class DataOutput { private Print print; public DataOutput(Print print){ this.print = print; } /** * 模拟导出,这里就是需要调用其他对象中的方法进行实现 * */ public void output(){ print.outPrint(); } public static void main(String[] args){ /** * 实例化工厂类 * */ PrintFactory printFactory = new PrintFactory(); /** * 实例化调用的类,通过构造方法来对DataOutput对象进行初始化 * */ DataOutput dataOutput = new DataOutput(printFactory.getPrint()); dataOutput.output(); } }
好了,接下来我们只要点击运行就会在控制台出现:使用excel进行导出;
如果由于需求的改变需要使用word来进行导出,很简单,我们只需要修改工厂类中的方法即可,其他都不需要变:
package xidian.sl.interfaces; import xidian.sl.impl.WordImpl; public class PrintFactory { /** * 用于获取print实例的方法 * */ public Print getPrint(){ /** * 这里默认返回的是excel方式进行导出 * */ //return new ExcelImpl(); /** * 更改为使用word方式进行导出 * */ return new WordImpl(); } }
到此我们已经实现了一个简单工厂模式,我们能够很明显的感觉到该模式的优势:让对象的调用者与对象创建过程进行分离,当对象调用者需要对象时只需直接向工厂请求即可,
从而避免了对象调用者与对象实现类以硬编码方式进行耦合。就如上面的程序,即使Print接口有很多实现类,我们只需要到工厂类中进行更换实现类的实例化即可,其他不需要
更改,这里也显示了面向接口编程的优势,这样对象的调用者就与接口进行耦合而不是与实现类,与接口耦合的好处就是接口可以有多个实现类,保证了我们可以不去修改接口,
而只是添加或者修改一个实现类即可;
下面我们将深入简单工厂模式:
用过spring框架的人都应该深有体会,spring本身就是一个巨大的工厂类,所有的bean都可以通过spring来进行管理,如DAOBean,ServiceBean,ActionBean等等;
下面来进行spring管理bean的演示,基本实现最简单的ioc容器
ApplicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean id = "print" class = "xidian.sl.interfaces.Print"> <!-- 为print注入普通属性值 --> <propertty name = "print" ref = "wordImpl"></propertty> </bean> <!-- 配置两个Bean实例 --> <bean id = "wordImpl" class = "xidian.sl.impl.WordImpl"></bean> <bean id = "excelImpl" class = "xidian.sl.impl.ExcelImpl"></bean> </beans>
获取指定bean的接口:
package xidian.sl.interfaces; public interface ApplicationContext { //获取指定Bean实例的方法 Object getBean(String name) throws Exception; }
该bean的实现类
package xidian.sl.impl; import java.io.File; import java.lang.reflect.Method; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; import xidian.sl.interfaces.ApplicationContext; /** * 以下使用Dom4j来进行xml文件的解析 * */ public class XmlApplicationContext implements ApplicationContext { /** * 保存容器中所有单例模式的bean实例,这里的hashMap是线程安全的 * */ private Map<String, Object> objPool = Collections.synchronizedMap(new HashMap<String, Object>()); //保存文件对应的Document对象 private Document document; //保存配置文件里的根元素 private Element root; public XmlApplicationContext(String filePath) throws Exception{ //使用dom4j读取配置文件 SAXReader reader = new SAXReader(); document = reader.read(new File(filePath)); //得到跟节点beans root = document.getRootElement(); //进行容器的初始化 initPool(); //初始化单例bean的属性 initProp(); } @Override public Object getBean(String name) throws Exception { Object target = objPool.get(name); //对于单例bean,容器已经初始化了所有的Bean实例 if(target.getClass() != String.class){ return target; }else{ String clazz = (String)target; //对于非单例的并未注入属性值 return Class.forName(clazz).newInstance(); } } /** * 初始化容器中的所有单例bean * */ private void initPool() throws Exception{ //遍历配置文件中的每个<bean ../>元素 for(Object obj: root.elements()){ Element beanElement = (Element)obj; //获取Bean元素中的id属性 String beanId = beanElement.attributeValue("id"); //获取bean元素中的class属性 String beanClazz = beanElement.attributeValue("class"); //获取bean元素中的scope属性 String beanScope = beanElement.attributeValue("scope"); //如果scope属性不存在或为singleton if(beanScope == null|| beanScope.equals("singleton")){ //以默认构造方法创建bean实例,并将其放入objPool中 objPool.put(beanId, Class.forName(beanClazz).newInstance()); //利用反射的技术 }else{ //对于非单例的,存放Bean实现类的类名 objPool.put(beanId, beanClazz); } } } /** * 初始化容器中的单例bean * */ private void initProp() throws Exception{ //遍历配置文件中的所有bean元素,即根节点的子节点 for(Object object: root.elements()){ Element beanElement = (Element)object; //获取Bean元素中的id属性 String beanId = beanElement.attributeValue("id"); //获取bean元素中的scope属性 String beanScope = beanElement.attributeValue("scope"); //如果scope属性不存在或为singleton if(beanScope == null|| beanScope.equals("singleton")){ //取出objPool的指定bean实例 Object bean = objPool.get(beanId); //遍历bean属性下的所有property属性 for(Object prop: beanElement.elements()){ Element probElement = (Element)prop; //获取property的name属性 String propName = probElement.attributeValue("name"); //获取property的value属性 String propValue = probElement.attributeValue("value"); //获取property的ref属性 String propRef = probElement.attributeValue("ref"); //将属性名的首字母大写 String propNameCamelize = propName.substring(0,1).toUpperCase() +propName.substring(1, propName.length()); //如果value值存在 if(propValue == null|| propValue.length()> 0){ //获取设置注入所需要的setter方法 java.lang.reflect.Method setter = bean.getClass().getMethod("set"+propNameCamelize, String.class); //执行setter注入 setter.invoke(bean, propValue); } if(propRef == null|| propRef.length()> 0){ //取得需要被注入的bean实例 Object target = objPool.get(propRef); //如果不存在 if(target == null){ //此处处理单例bean依赖于非单例bean的情形 } //定义设值注入需要的setter方法 Method setter = null; //遍历target对象所实现的所有方法 for(Class superInterface: target.getClass().getInterfaces()){ try{ //获取设置注入所需要的setter方法 setter = bean.getClass().getMethod("set"+propNameCamelize, superInterface); //如果成功获取,跳出循环 break; }catch (Exception e) { //如果没有找到就继续下次循环 continue; } } //如果setter方法依然是null,直接取得target的实现类对应的setter方法 if(setter == null){ setter = bean.getClass().getMethod("set"+propNameCamelize, target.getClass()); } //执行setter注入 setter.invoke(bean, target); } } } } } }
最后进行注入bean的测试:
package xidian.sl.servlet; import xidian.sl.impl.XmlApplicationContext; import xidian.sl.interfaces.ApplicationContext; import xidian.sl.interfaces.Print; public class IocTest { public static void main(String[] args) throws Exception{ //创建IOC容器 ApplicationContext ctx = new XmlApplicationContext("applicationContext.xml"); //从IOC容器中取出print bean Print print = (Print)ctx.getBean("print"); //测试Print对象 print.outPrint(); } }
总结下简单工厂模式:不知道大家有没有发现一个问题就是上面管理实例分配的工厂类中,现在只能每次为其分配一个实例,如果要配置多个实例就需要在工厂类中就行逻辑
判断:
package xidian.sl.interfaces; import xidian.sl.impl.ExcelImpl; import xidian.sl.impl.WordImpl; public class PrintFactory { /** * 用于获取print实例的方法 * */ public Print getPrint(Integer param){ if(param == 1){ /** * 这里默认返回的是excel方式进行导出 * */ return new ExcelImpl(); }else if(param == 2){ /** * 更改为使用word方式进行导出 * */ return new WordImpl(); }else{ return null; } } }
简单工厂模式在java中的应用:
1.DateFormat:jdk中的一个工具类java.text.DateFormat,用来格式化一个本地日期与时间
通过源码我们可以知道DateFormat是一个抽象类(abstract),下面的源代码就是这个类里包含的方法,其实这些就是静态工厂方法,通过静态方法来提供自己的实例是完全可以的(抽象类本身不能进行实例化)。从源码可以看出getDateInstance()方法做了两件事情:
一.运用了多态性:由于SimpleDateFormat是DateFormat的子类,而getDateInstance()声明的类型为DateFormat而实际返回类型为子类SimpleDateFormat
二.使用了静态工厂方法(static):由于DateFormat是抽象类不能进行实例化,因此也就不能调用其中的普通方法(非静态方法)。因此我们必须将其声明为static,才能返回实例
通过上面做的两件事情就将具体子类的实例化过程隐藏起来了,调用者不必考虑具体子类的实例化,因为抽象类会提供它的合适子类实例
源代码:
/** * Gets the date formatter with the default formatting style * for the default locale. * @return a date formatter. */ public final static DateFormat getDateInstance() { return get(0, DEFAULT, 2, Locale.getDefault()); } /** * Gets the date formatter with the given formatting style * for the default locale. * @param style the given formatting style. For example, * SHORT for "M/d/yy" in the US locale. * @return a date formatter. */ public final static DateFormat getDateInstance(int style) { return get(0, style, 2, Locale.getDefault()); } /** * Gets the date formatter with the given formatting style * for the given locale. * @param style the given formatting style. For example, * SHORT for "M/d/yy" in the US locale. * @param aLocale the given locale. * @return a date formatter. */ public final static DateFormat getDateInstance(int style, Locale aLocale) { return get(0, style, 2, aLocale); } /** * Creates a DateFormat with the given time and/or date style in the given * locale. * @param timeStyle a value from 0 to 3 indicating the time format, * ignored if flags is 2 * @param dateStyle a value from 0 to 3 indicating the time format, * ignored if flags is 1 * @param flags either 1 for a time format, 2 for a date format, * or 3 for a date/time format * @param loc the locale for the format */ private static DateFormat get(int timeStyle, int dateStyle, int flags, Locale loc) { if ((flags & 1) != 0) { if (timeStyle < 0 || timeStyle > 3) { throw new IllegalArgumentException("Illegal time style " + timeStyle); } } else { timeStyle = -1; } if ((flags & 2) != 0) { if (dateStyle < 0 || dateStyle > 3) { throw new IllegalArgumentException("Illegal date style " + dateStyle); } } else { dateStyle = -1; } try { // Check whether a provider can provide an implementation that's closer // to the requested locale than what the Java runtime itself can provide. LocaleServiceProviderPool pool = LocaleServiceProviderPool.getPool(DateFormatProvider.class); if (pool.hasProviders()) { DateFormat providersInstance = pool.getLocalizedObject( DateFormatGetter.INSTANCE, loc, timeStyle, dateStyle, flags); if (providersInstance != null) { return providersInstance; } } return new SimpleDateFormat(timeStyle, dateStyle, loc); } catch (MissingResourceException e) { return new SimpleDateFormat("M/d/yy h:mm a"); } }
以上那个应用其实是将工厂角色与抽象产品角色进行合并:也就是说一个抽象产品类同时也是子类的工厂类。
还有一种退化的简单工厂模式就是,将抽象产品类,具体产品类,工厂类三个角色都进行合并,举例:
package xidian.sl.impl; public class Product { public Product(){}; /** * 静态工厂方法来返回自己的实例 * */ public static Product getInstance(){ return new Product(); } }
其实这个跟单例模式已经很像了,只要稍加修改就可以实现一个单例模式了
简单工厂模式的有缺点:
优点:该模式的核心就是工厂类,这个类中含有必要的判断逻辑,可以决定在什么时候创建哪个产品类的实例。客户端可以免除直接创建产品对象的责任。
缺点:1.工厂类集中了所有产品创建逻辑,形成一个无所不知的全能类,对于这样的类我们很难进行控制
通过在调用工厂类方法是传入类型参数来判断到底是要返回哪种实例;但复杂的逻辑判断一般我们都是要舍弃的;
我的下一遍博客就会解决这个问题:根据根据每个实例生成一个工厂类的工厂方法模式:http://www.cnblogs.com/shenliang123/archive/2012/05/10/2494826.html