設計模式學習02—工廠模式

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創建對象,但是在現實編程中,大部分工廠都是配合反射來使用的,可以考慮將要創建的產品屬性,設置工廠屬性放到配置文件中,程序啓動就將對象創建好,這樣當增加一個簡單產品時,可以做到修改配置文件即可,就算增加複雜產品,只需要新寫一個工廠類,配置配置就行,而不用大量修改源碼。
     結束語,工廠方法其實在項目中使用非常非常頻繁,這個模式幾乎人盡皆知,但卻不是每個人都能用好,工廠模式通常和其他模式混合使用,變化出無窮的優秀設計。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章