簡單工廠模式就是將多個類對象交給工廠類來生成的設計方式
在不使用工廠模式前我們一般在某個類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
