天天看點

設計模式——工廠方法模式【Factory Method Pattern】

女娲補天的故事大家都聽說過吧,今天不說這個,說女娲創造人的故事,可不是“造人”的工作,這個詞被現代人濫用了。這個故事是說,女娲在補了天後,下到凡間一看,哇塞,風景太優美了,天空是湛藍的,水是清澈的,空氣是清新的,太美麗了,然後就待時間長了就有點寂寞了,沒有動物,這些看的到都是靜态的東西呀,怎麼辦?别忘了是神仙呀,沒有辦不到的事情,于是女娲就架起了八卦爐(技術術語:建立工廠)開始建立人,具體過程是這樣的:先是泥巴捏,然後放八卦爐裡烤,再扔到地上成長,但是意外總是會産生的:

第一次烤泥人,茲茲茲茲~~,感覺應該熟了,往地上一扔,biu~,一個白人誕生了,沒烤熟!

第二次烤泥人,茲茲茲茲茲茲茲茲~~,上次都沒烤熟,這次多烤會兒,往地上一扔,嘿,熟過頭了,黑人哪!

第三次烤泥人,茲~茲~茲~,一邊烤一邊看着,嘿,正正好,Perfect!優品,黃色人類!【備注:RB人不屬此列】

這個過程還是比較有意思的,先看看類圖:

設計模式——工廠方法模式【Factory Method Pattern】

那這個過程我們就用程式來表現,首先定義一個人類的總稱:

package factory.pattern;

/**

* 定義一個人類的統稱

*/

public interface Human {

//人是愉快的,會笑的,本來是想用smile表示,想了一下laugh更合适,好長時間沒有大笑了;

public void laugh();

//人類還會哭,代表痛苦

public void cry();

//人類會說話

public void talk();

}

然後定義具體的人類:

package factory.pattern;

/**

* 黃色人類,這個翻譯的不準确,将就點吧

*/

public class YellowHuman implements Human {

public void cry() {

System.out.println("黃色人類會哭");

}

public void laugh() {

System.out.println("黃色人類會大笑,幸福呀!");

}

public void talk() {

System.out.println("黃色人類會說話,一般說的都是雙位元組");

}

白色人類:

package factory.pattern;

/**

* 白色人類

*/

public class WhiteHuman implements Human {

public void cry() {

System.out.println("白色人類會哭");

}

public void laugh() {

System.out.println("白色人類會大笑,侵略的笑聲");

}

public void talk() {

System.out.println("白色人類會說話,一般都是但是單位元組!");

}

黑色人類:

package factory.pattern;

/**

* 黑色人類

*/

public class BlackHuman implements Human {

public void cry() {

System.out.println("黑人會哭");

}

public void laugh() {

System.out.println("黑人會笑");

}

public void talk() {

System.out.println("黑人可以說話,一般人聽不懂");

}

人類也定義完畢了,那我們把八卦爐定義出來:

package factory.pattern;

/**

* 今天講女娲造人的故事

*/

public class HumanFactory {

public static Human createHuman(Class c){

Human human = null; //定義一個類型的人類

try {

human = (Human)Class.forName(c.getName()).newInstance(); //産生一個人類

} catch (InstantiationException e) {//你要是不說個人類顔色的話,沒法烤,要白的黑,你說話了才好烤

System.out.println("必須指定人類的顔色");

} catch (IllegalAccessException e) { //定義的人類有問題,那就烤不出來了,這是...

System.out.println("人類定義錯誤!");

} catch (ClassNotFoundException e) { //你随便說個人類,我到哪裡給你制造去?!

System.out.println("混蛋,你指定的人類找不到!");

}

return human;

}

然後我們再把女娲聲明出來:

package factory.pattern;

/**

* 首先定義女娲,這真是額的神呀

*/

public class NvWa {

public static void main(String[] args) {

//女娲第一次造人,試驗性質,少造點,火候不足,缺陷産品

System.out.println("------------造出的第一批人是這樣的:白人-----------------");

Human whiteHuman = HumanFactory.createHuman(WhiteHuman.class);

whiteHuman.cry();

whiteHuman.laugh();

whiteHuman.talk();

//女娲第二次造人,火候加足點,然後又出了個次品,黑人

System.out.println("/n/n------------造出的第二批人是這樣的:黑人-----------------");

Human blackHuman = HumanFactory.createHuman(BlackHuman.class);

blackHuman.cry();

blackHuman.laugh();

blackHuman.talk();

//第三批人了,這次火候掌握的正好,黃色人類(不寫黃人,免得引起歧義),備注:RB人不屬于此列

System.out.println("/n/n------------造出的第三批人是這樣的:黃色人類-----------------");

Human yellowHuman = HumanFactory.createHuman(YellowHuman.class);

yellowHuman.cry();

yellowHuman.laugh();

yellowHuman.talk();

}

這樣這個世界就熱鬧起來了,人也有了,但是這樣建立太累了,神仙也會累的,那怎麼辦?神仙就想了:我塞進去一團泥巴,随機出來一群人,管他是黑人、白人、黃人,隻要是人就成(你看看,神仙都偷懶,何況是我們人),先修改類圖:

設計模式——工廠方法模式【Factory Method Pattern】

然後看我們的程式修改,先修改HumanFactory.java,增加了createHuman()方法:

//女娲生氣了,把一團泥巴塞到八卦爐,哎産生啥人類就啥人類

public static Human createHuman(){

Human human = null; //定義一個類型的人類

//首先是獲得有多少個實作類,多少個人類

List<Class> concreteHumanList = ClassUtils.getAllClassByInterface(Human.class); //定義了多少人類

//八卦爐自己開始想燒出什麼人就什麼人

Random random = new Random();

int rand = random.nextInt(concreteHumanList.size());

human = createHuman(concreteHumanList.get(rand));

return human;

}

然後看女娲是如何做的:

//女娲煩躁了,愛是啥人類就是啥人類,燒吧

for(int i=0;i<100;i++){

System.out.println("/n/n------------随機産生人類了-----------------" + i);

Human human = HumanFactory.createHuman();

human.cry();

human.laugh();

human.talk();

}

哇,這個世界熱鬧了!,不過還沒有完畢,這個程式你跑不起來,還要有這個類:

package factory.pattern;

import java.io.File;

import java.io.IOException;

import java.net.URL;

import java.util.ArrayList;

import java.util.Enumeration;

import java.util.List;

public class ClassUtils {

// 給一個接口,傳回這個接口的所有實作類

public static List<Class> getAllClassByInterface(Class c) {

List<Class> returnClassList = new ArrayList<Class>(); // 傳回結果

// 如果不是一個接口,則不做處理

if (c.isInterface()) {

String packageName = c.getPackage().getName(); // 獲得目前的包名

try {

List<Class> allClass = getClasses(packageName); // 獲得目前包下以及子包下的所有類

// 判斷是否是同一個接口

for (int i = 0; i < allClass.size(); i++) {

if (c.isAssignableFrom(allClass.get(i))) { // 判斷是不是一個接口

if (!c.equals(allClass.get(i))) { // 本身不加進去

returnClassList.add(allClass.get(i));

}

}

}

} catch (ClassNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

return returnClassList;

}

// 從一個包中查找出所有的類,在jar包中不能查找

private static List<Class> getClasses(String packageName)

throws ClassNotFoundException, IOException {

ClassLoader classLoader = Thread.currentThread()

.getContextClassLoader();

String path = packageName.replace('.', '/');

Enumeration<URL> resources = classLoader.getResources(path);

List<File> dirs = new ArrayList<File>();

while (resources.hasMoreElements()) {

URL resource = resources.nextElement();

dirs.add(new File(resource.getFile()));

}

ArrayList<Class> classes = new ArrayList<Class>();

for (File directory : dirs) {

classes.addAll(findClasses(directory, packageName));

}

return classes;

}

private static List<Class> findClasses(File directory, String packageName)

throws ClassNotFoundException {

List<Class> classes = new ArrayList<Class>();

if (!directory.exists()) {

return classes;

}

File[] files = directory.listFiles();

for (File file : files) {

if (file.isDirectory()) {

assert !file.getName().contains(".");

classes.addAll(findClasses(file, packageName + "."

+ file.getName()));

} else if (file.getName().endsWith(".class")) {

classes.add(Class.forName(packageName

+ '.'

+ file.getName().substring(0,

file.getName().length() - 6)));

}

}

return classes;

}

}

告訴你了,這個ClassUtils可是個寶,用處可大了去了,可以由一個接口查找到所有的實作類,也可以由父類查找到所有的子類,這個要自己修改一下,動腦筋想下,簡單的很!完整的類圖如下:

設計模式——工廠方法模式【Factory Method Pattern】

我們來總結一下,特别是增加了createHuman()後,是不是這個工廠的擴充性更好了?你看你要再加一個人類,隻要你繼續內建Human接口成了,然後啥都不用修改就可以生産了,具體産多少,那要八卦爐說了算,簡單工廠模式就是這麼簡單,那我們再引入一個問題:人是有性别的呀,有男有女,你這怎麼沒差別,别急,這個且聽下回分解!

工廠方法模式還有一個非常重要的應用,就是延遲始化(Lazy initialization),什麼是延遲始化呢?一個對象初始化完畢後就不釋放,等到再次用到得就不用再次初始化了,直接從記憶體過中拿到就可以了,怎麼實作呢,很簡單,看例子:

private static HashMap<String,Human> humans = new HashMap<String,Human>();

//如果MAP中有,則直接從取出,不用初始化了

if(humans.containsKey(c.getSimpleName())){

human = humans.get(c.getSimpleName());

}else{

human = (Human)Class.forName(c.getName()).newInstance();

//放到MAP中

humans.put(c.getSimpleName(), human);

}

很簡單,就加上了黃色那部分的代碼,這個在類初始化很消耗資源的情況比較實用,比如你要連接配接硬體,或者是為了初始化一個類需要準備比較多條件(參數),通過這種方式可以很好的減少項目的複雜程度。