1、動機與定義
我們在程序中使用一個對象時,需要new一下,如果需要設置其他值就再初始化一下。比如我要使用一個按鈕,手動new一個矩形按鈕,然後初始化一些值,如顯示文字,背景色等。
// 矩形按鈕
IButton btn = new RecButton();
// 初始化其他值
btn.setText("提交");
btn.setBackgroundColor("#00aaff");
// 其他初始化省略
// 圓形按鈕
IButton btn2 = new RoundButton();
btn.setText("關於");
btn.setBackgroundColor("#00aaff");
// 其他初始化省略
這樣寫有幾個缺點:
1、寫一次沒有問題,如果需要100個,就要寫100次,太麻煩了。
2、很多設置是重複的,比如例子中的背景色,一個系統可能風格統一,只有幾種背景色,不需要每次都手動設置。
3、耦合性太強,客戶端必須知道具體的按鈕創建過程,必須會創建按鈕才行,後續按鈕的方法改變,客戶端也可要跟着修改,如以後按鈕必須設置大小了,所有客戶端代碼都要變動。
4、重複對象沒有控制,比如btn2的關於,可能每個頁面都有,但是每個頁面的這個按鈕都是一模一樣的,沒必要每次都創建一遍。
5、沒有封裝變化,假如寫了100個new RoundButton,後續這個按鈕發生改變了,我們要改100處代碼。
等等,如果你再仔細想想,各種各樣的情況下都有各種各樣的缺點(當然這麼寫也有優點的,至少簡單嘛,如何設計沒有最好,只有合適的),那麼我們有沒有其他方式來規避這些問題呢?其實我們需要一個對象時,除了自己new之外,還有就是從其他地方獲取,我們完全可以把這些按鈕的創建過程放到一起,客戶端使用的時候直接獲取就行了。比如下面代碼:
public class RoundButtonFactory implements Creater {
public static IButton createButton(String text) {
// 圓形按鈕
IButton btn = new RoundButton();
btn.setText(text);
btn.setBackgroundColor("#00aaff");
return btn;
}
}
工廠模式定義:定義一個用於創建對象的接口,讓子類決定實例化哪一個類,工廠方法使一個類的實例化延遲到其子類。
客戶端使用時,只需要調用createButton就行,屏蔽了底層的具體實現,後續實現類變化了,只需要修改這個方法就行了;也使客戶端和具體的按鈕實現類解耦開來,其實這就是最基本的工廠模式。
簡單說就是將要使用的對象抽象出一個接口(產品),還有一個接口的創建工廠,每個具體實現類(產品的實現類)的創建由工廠的實現類創建。
2、結構與類圖
工廠模式通用類圖如下:
上面舉的例子的類圖如下:
工廠方法包含四個角色:
1、抽象產品(Product):負責定義產品公有屬性;
2、具體產品(ConcreteProduct):具體的產品實現類;
3、抽象工廠(Creater):抽象的創建類,也就是抽象工廠;
4、具體工廠(ConcreteCreater):具體創建者,也就是具體工廠,負責具體產品實現類的創建。
3、適用場景及效果(優缺點)
沒有工廠的時候,假如我們要做飯,需要用到火,創建火的同時發現需要用到木柴,還要創建一個鋸來鋸木柴......代碼如下:
// 創建鋸
Saw saw = new Saw ();
// 使用鋸,鋸木柴
FireWood fw = saw.cut();
// 使用木柴創建火
Fire fire = new WoodFire(fw);
可以看到,這樣的話,做飯的邏輯就依賴了鋸、木柴、火等東西,如果使用工廠呢
Fire fire = WoodFireFactroy.create();
1、具有良好的封裝性,邏輯代碼清晰,不用new了,不用初始化了,只需要簡單的get或者create就行了。
2、耦合性低,工廠模式是典型的解耦框架,屏蔽了具體產品類(都是用產品接口嘛),調用者無需關心底層如何實現,產品類變化時只要接口不變就不影響調用者,使客戶端和具體產品解耦,更能屏蔽創建具體產品時需要的其他關聯類(如做飯就不需要依賴鋸了),符合迪米特法則,只和需要的類交流,也符合依賴導致原則,只依賴抽象,更符合里氏替換原則,使用產品子類代替父類,完全沒問題。
3、擴展非常方便,新加一類產品時(如上面例子新增一種按鈕),只需要新增一個工廠實現類即可。無需修改原有代碼,達到了“擁抱變化”,符合開閉原則。
當產品創建簡單,比較固定的時候,或者調用者個性化情況太多時,工廠就體現不出他的優勢了,比如常用的List,我們就沒必要弄個ListFactory.createArrayList(),徒增代碼複雜性。還有種類比較固定,不會太多,沒必要抽象出接口,那也不必非用工廠模式。最好在下面的情況下才考慮使用工廠模式:
1、調用者不需要直到具體的產品創建過程時;
2、調用者使用的對象存在變動的可能,甚至完全不知道使用哪個具體對象時。
3、需要做出靈活、可擴展的功能時,再考慮工廠模式,其實不一定所有功能都要做到可擴展,謹慎過度設計。
4、需要解耦時,減少調用者和具體實現類的依賴時。
4、示例和擴展
1、退化成簡單工廠模式,當要創建的產品種類較少時,並且可以預見時,可以把工廠實現類合併到一起,對外提供一個靜態工廠方法,比如上面的按鈕例子中:
public static IButton crateButton(String type, String text) {
IButton btn = null;
if ("round".equals(type)) {
btn = new RoundButton(); // 圓形按鈕
} else if ("rec".equals(type)) {
btn = new RecButton(); // 矩形按鈕
} else {
return null ;
}
btn.setText(text);
btn.setBackgroundColor("#00aaff");
return btn;
}
這種簡單工廠模式用起來非常簡單,缺點是擴展困難,不符合開閉原則,要注意設計沒有最好,只有適不適合,在可預見的變化下,簡單工廠模式非常好用。
2、約束產品類實例數量,通常和其他模式組合能達到很多效果,比如使用工廠創建好對象後緩存起來,達到單例或多例的目的,比如下面單例工廠:
//單例工廠
public class SingletonFactory {
private static Map<Class<?>, Object> objCache = new HashMap<Class<?>, Object>();
public synchronized static Object getInstance(Class<?> clazz) throws Exception {
Object singleton = objCache.get(clazz);
if (singleton == null) {
singleton = createInstance(clazz);
objCache.put(clazz, singleton);
}
return singleton;
}
private static Object createInstance(Class<?> clazz) throws Exception {
Constructor ct = clazz.getDeclaredConstructor();
ct.setAccessible( true);
return ct.newInstance();
}
}
3、多工廠協調,工廠模式中,一個工廠創建一個產品也行,創建多個產品也行,當產品種類過多時,如果工廠類也較多,此時最好弄一個協調類來協調,方便調用者使用,而不是讓調用者逐個去找工廠類。
單例可以,多例也就沒問題了,比如數據庫連接池,設置最大100個,使用工廠模式就很有效,此時需要考慮每個實例的狀態,使用中的話不能被獲取等等。
4、延遲實例化,有的時候產品創建和銷燬比較耗費資源,可以考慮創建好之後緩存起來,用完之後不銷燬,或者使用完畢後將對象改成初始狀態,而不是重新創建,方便後續使用,還是連接池的例子,如果用完了,是不銷燬的,還會重新使用。
5、結合反射或配置文件,代替程序new。雖然例子中我們使用的是new創建對象,但是在現實編程中,大部分工廠都是配合反射來使用的,可以考慮將要創建的產品屬性,設置工廠屬性放到配置文件中,程序啓動就將對象創建好,這樣當增加一個簡單產品時,可以做到修改配置文件即可,就算增加複雜產品,只需要新寫一個工廠類,配置配置就行,而不用大量修改源碼。
結束語,工廠方法其實在項目中使用非常非常頻繁,這個模式幾乎人盡皆知,但卻不是每個人都能用好,工廠模式通常和其他模式混合使用,變化出無窮的優秀設計。