工廠方法模式詳解

工廠模式

1拓售、故事背景介紹

東漢《風(fēng)俗通》記錄了一則神話故事:“開天辟地兽肤, 未有人民折剃,女媧搏黃土做人”另假,講述的內(nèi)容就是大家非常熟悉的女媧造人的故事。開天辟地之初怕犁,大地上并沒有生物边篮,只有蒼茫大地,純粹而潔凈的自然環(huán)境奏甫,寂靜而又寂寞戈轿,于是女媧決定創(chuàng)造一個新物種 (即人類)來增加世界的繁榮,怎么制造呢阵子?
別忘了女媧是神仙思杯,沒有辦不到的事情,造人的過程是這樣的:首先款筑,女媧采集黃土捏成人的形狀智蝠,然后放到八卦爐中燒制,最后放置到大地上生長奈梳,工藝過程是沒有錯的杈湾,但是意外隨時都會發(fā)生:

  • 第一次烤泥人,感覺應(yīng)該熟了攘须,往大地上一放漆撞,哇,沒烤熟于是一個白人誕生了S谥妗(這也是缺乏經(jīng)驗的最好證明)

  • 第二次烤泥人浮驳,上一次沒烤熟,這次多烤一會兒捞魁,放到世間一看至会,嘿,熟過頭了谱俭,于是黑人誕生了奉件!

  • 第三次烤泥人宵蛀,一邊燒制一邊察看,直到表皮微黃县貌,嘿术陶,真正好,于是黃色人種出現(xiàn)了煤痕!

    這個造人過程是比較有意思的梧宫,是不是可以通過軟件開發(fā)來實現(xiàn)這個過程呢?古人云:“三人行摆碉,必有我?guī)熝伞碧料唬诿嫦驅(qū)ο蟮乃季S中,萬物皆對象兆解,是對象我們就可以通過軟件設(shè)計來實現(xiàn)馆铁。
    首先我們對造人過程進行分析跑揉,該過程涉及三個對象:女媧锅睛、八卦爐、三種不同膚色的人历谍。女媧可以使用場景類Client(客戶现拒、使用者)來表示,八卦爐類似于一個工廠望侈,負(fù)責(zé)制造生產(chǎn)產(chǎn)品(即人類)印蔬,三種不同膚色的人,他們都是同一個接口下的不同實現(xiàn)類脱衙,都是人類侥猬,只是膚色、語言不同捐韩,對于八卦爐來說都是它生產(chǎn)出的產(chǎn)品退唠。
    分析完畢,我們就可以畫出如圖所示的類圖荤胁。

圖片.png

類圖比較簡單瞧预,AbstractHumanFactory是一個抽象類,定義了一個八卦爐具有的整體功能仅政,HumanFactory為實現(xiàn)類垢油,完成具體的任務(wù)一創(chuàng)建人類;Human接口是人類的總稱圆丹,其三個實現(xiàn)類分別為三類人種滩愁;NvWa類是一個場景類,負(fù)責(zé)模擬這個場景辫封,執(zhí)行相關(guān)的任務(wù)硝枉。我們定義的每個人種都有兩個方法: getColor (獲得人的皮膚顏色)和talk (交談)玖瘸,其源代碼如下所示。

2檀咙、代碼示例如下

2.1 人類接口

//人類總稱
public interface Human {
    //每個人種的皮膚都有相應(yīng)的顏色
    public void getColor();
    //人類會說話
    public void talk();
}

2.2 人種類別

每個人種都至少具有兩個方法雅倒,黑色人種、黃色人種弧可、白色人種的代碼分別如下蔑匣。

2.2.1 黑色人種
//黑色人種
public c1ass B1ackHuman implements Human {
    
    public void getColor() {
        System.out.print1n("黑色人種的皮膚顏色是黑色的! ") ;
    }
    
    public void talk() {
        System.out.println("黑人會說話,一般人聽不懂裁良。");
    }
}
2.2.2 黃色人種
//黃色人種
public class Ye11 owHuman implements Human {
    
    public void getColor() {
        System .out.println("黃色人種的皮膚顏色是黃色的! ");
    }
    
    public void ta1k() {
        system.out.print1n("黃色人種會說話,一般說的都是雙字節(jié)校套。");
    }
}
2.2.3 白色人種
//白色人種
public class WhiteHuman implements Human {
    
    public void getColor(){
        System.out.printin("自色人種的皮膚顏色是白色的! ");
    }
    
    public void talk() {
        System.out.printIn("白色人種會說話笛匙,一般都是但是單字節(jié)妹孙。");
    }
}

2.3 八卦爐(工廠)

所有的人種定義完畢秋柄,下一步就是定義一個八卦爐,然后燒制人類蠢正。我們想象一下骇笔,女媧最可能給八卦爐下達(dá)什么樣的生產(chǎn)命令呢?應(yīng)該是”給我生產(chǎn)出一個黃色人種( YellowHuman類 )”嚣崭,而不會是“給我生產(chǎn)一個會走笨触、會跑、會說話雹舀、皮膚是黃色的人種”芦劣,因為這樣的命令增加了交流的成本,作為一個生產(chǎn)的管理者葱跋,只要知道生產(chǎn)什么就可以了持寄,而不需要事物的具體信息。通過分析娱俺,我們發(fā)現(xiàn)八卦爐生產(chǎn)人類的方法輸人參數(shù)類型應(yīng)該是Human接口的實現(xiàn)類稍味,這也解釋了為什么類圖上的 AbstractHumanFactory 抽象類中 createHuman 方法的參數(shù)為 Class類型。其源代碼如下所示荠卷。

//抽象人類創(chuàng)建工廠
public abstract class AbstractHumanFactory {
    public abstract <T extends Human> T createHuman (Class<T> c);
}

注意模庐,我們在這里采用了泛型(Generic), 通過定義泛型對tcreateHuman的輸人參數(shù)產(chǎn)生兩層限制:

  • 必須是Class類型;

  • 必須是Human的實現(xiàn)類。

    其中的 “?” 表示的是油宜,只要實現(xiàn)了Human接口的類都可以作為參數(shù)掂碱,泛型是JDK 1.5中的一個非常重要的新特性怜姿,它減少了對象間的轉(zhuǎn)換,約束其輸入?yún)?shù)類型疼燥,對Collection集合下的實現(xiàn)類都可以定義泛型沧卢。有關(guān)泛型的詳細(xì)知識,請參考相關(guān)的Java語法文檔醉者。目前女媧只有一個八卦爐但狭,其實現(xiàn)生產(chǎn)人類的方法,如下所示撬即。

public class HumanFactory extends AbstractHumanFactory{
    
    public <T extends Human> T  createteHuman(Class<T> c){
        //定義一個生產(chǎn)的人種
        Human human = null;
        try {
            //產(chǎn)生一個人種
            human = (Human)Class.forName(c.gettName()).newInstance);
        } catch(Exception e){
            System.out.Println("人種生成錯誤!");
        }
        return (T)human;
    }
}

2.3.1 造人過程(客戶)

人種有了立磁,八卦爐也有了,剩下的工作就是女媧采集黃土剥槐,然后命令八卦爐開始生產(chǎn)唱歧,其過程如下所示。

public class NvWa {
    public static void main(string[] args) {
        //聲明陰陽八卦爐
        AbstractHumanFactory YinYangLu = new Humanractory(); 
        //女媧第一次道人粒竖,火候不足颅崩,于是白人產(chǎn)生了
        System.out.println("--造出的第一批人是白色人種--");
        Human whiteHuman = yinYangLu.createHuman(WhiteHuman.class);
        whiteHuman.getColor();
        whiteHuman.ta1k();
        //女媧第二次道人,火候過足温圆,于是黑人產(chǎn)生了
        System.out.println("--造出的第二批人是黑色人種--");
        Human blackHuman = yinYangLu.createHuman(BlackHuman.class);
        blackHuman.getColor();
        blackHuman.ta1k();
        //女媧第三次道人挨摸,火候剛好,于是黃色人種產(chǎn)生了
        System.out.println("--造出的第三批人是黃色人種--");
        Human yelloHuman = yinYangLu.createHuman(YelloHuman.class);
        yelloHuman.getColor();
        yelloHuman.ta1k();
    }
}

但岁歉,這就結(jié)束了嗎?請接著往下看膝蜈。

2.4 八卦爐改造(簡單工廠模式)

我們這樣考慮一一個問題:一個模塊僅需要一個工廠類锅移,沒有必要把它產(chǎn)生出來,使用靜態(tài)的方法就可以了饱搏,根據(jù)這一要求非剃,我們把上例中的 AbstarctHumanFactory 修改一下,代碼如下

public class HumanFactory{
    //將工廠創(chuàng)建對象的方法設(shè)置為靜態(tài)
    public static <T extends Human> T  createteHuman(Class<T> c){
        //定義一個生產(chǎn)的人種
        Human human = null;
        try {
            //產(chǎn)生一個人種
            human = (Human)Class.forName(c.gettName()).newInstance);
        } catch(Exception e){
            System.out.Println("人種生成錯誤!");
        }
        return (T)human;
    }
}

這樣推沸,我們的女媧在使用八卦爐時就不用去創(chuàng)建八卦爐對象了备绽,可以直接使用Humanractory去創(chuàng)建人類了。

public class NvWa {
    public static void main(string[] args) {
        //女媧第一次道人鬓催,火候不足肺素,于是白人產(chǎn)生了
        System.out.println("--造出的第一批人是白色人種--");
        Human whiteHuman = Humanractory.createHuman(WhiteHuman.class);
        whiteHuman.getColor();
        whiteHuman.ta1k();
        //女媧第二次道人,火候過足宇驾,于是黑人產(chǎn)生了
        System.out.println("--造出的第二批人是黑色人種--");
        Human blackHuman = Humanractory.createHuman(BlackHuman.class);
        blackHuman.getColor();
        blackHuman.ta1k();
        //女媧第三次道人倍靡,火候剛好,于是黃色人種產(chǎn)生了
        System.out.println("--造出的第三批人是黃色人種--");
        Human yelloHuman = Humanractory.createHuman(YelloHuman.class);
        yelloHuman.getColor();
        yelloHuman.ta1k();
    }
}

2.5 多幾個八卦爐(多工廠模式)

當(dāng)我們在做一個比較復(fù)雜的項目時课舍,經(jīng)常會遇到初始化一個對象很耗費精力的情況塌西,所有的產(chǎn)品類都放到一個工廠方法中進行初始化會使代碼結(jié)構(gòu)不清晰他挎。例如,一個產(chǎn)品類有5個具體實現(xiàn)捡需,每個實現(xiàn)類的初始化(不僅僅是new办桨,初始化包括new一個對象,并對對象設(shè)置一定的初始值)方法都不相同站辉,如果寫在一個工廠方法中崔挖,勢必會導(dǎo)致該方法巨大無比,那該怎么辦?
考慮到需要結(jié)構(gòu)清晰庵寞,我們就為每個產(chǎn)品定義一個創(chuàng)造者狸相,然后由調(diào)用者自己去選擇與哪個工廠方法關(guān)聯(lián)。我們還是以女媧造人為例捐川,每個人種都有一個固定的八卦爐脓鹃,分別造出黑色人種、白色人種古沥、黃色人種瘸右。

2.5.1 多工廠模式的抽象工廠類代碼
//多工廠模式的抽象工廠類
public abstract class AbstractHumanFactory{
    public abstract Human creteHuman();
}
2.5.2 各色人種的創(chuàng)建工廠如代碼所示。
//黑色人種的創(chuàng)建工廠實現(xiàn)
public class BlackHumanFactory extends AbstractHumanFactory {
    public Human createHuman() {
        return new BlackHuman();
    }
}
//黃色人種的創(chuàng)建工廠實現(xiàn)
public class Ye1lowHumanFactory extends AbstractHumanFactory {
    public Human createHuman() {
        return new Ye1loWHuman();
    }
}
//白色人種的創(chuàng)建工廠實現(xiàn)
public class YellowHumanFactory extends AbstractHumanFactory {
    public Human createHuman() {
        return new WhiteHuman();
}
2.5.3 女媧多了好多爐子
public class NvWa {
    public static void main(String[] args) {
        //女媧第一次造人岩齿,火候不足太颤,于是白色人種產(chǎn)生了
        System.out.print1n("--造出的第一批人是白色人種--");
        Human whiteHuman = (new whlteHumanractory()).createHuman();
        whiteHuman.getColor();
        whiteHuman.talk();
        //女媧第二次造人,火候過足盹沈,于是黑色人種產(chǎn)生了
        System.out.println("--造出的第二批人是黑色人種--") ;
        Human b1ackHuman = (new BlackHumanFactory()).createHuman();
        blackHuman.getColor();
        blackHuman.talk();
        //第三次造人龄章,火候剛剛好,于是黃色人種產(chǎn)生了
        System.out.println("--造出的第三批人是黃色人種--");
        Human yellowHuman = (new YellowHumanFactory()).createRuman();
        yellowHuman.getColor();
        yellowHuman.talk();
    }
}

運行結(jié)果還是相同乞封。我們回顧一下做裙, 每一個產(chǎn)品類都對應(yīng)了一個創(chuàng)建類,好處就是創(chuàng)建類的職責(zé)清晰肃晚,而且結(jié)構(gòu)簡單锚贱,但是給可擴展性和可維護性帶來了一定的影響。為什么這么說呢关串?
如果要擴展一個產(chǎn)品類拧廊,就需要建立一個相應(yīng)的工廠類,這樣就增加了擴展的難度晋修。因為工廠類和產(chǎn)品類的數(shù)量相同吧碾,維護時需要考慮兩個對象之間的關(guān)系。
當(dāng)然飞蚓,在復(fù)雜的應(yīng)用中一般采用多工廠的方法滤港,然后再增加一個協(xié)調(diào)類,避免調(diào)用者與各個子工廠交流,協(xié)調(diào)類的作用是封裝子工廠類,對高層模塊提供統(tǒng)一的訪問接口溅漾。

3山叮、拓展工廠

3.1 替代單例模式

拋開上面的背景故事,回到單例模式添履,我們是不是可以采用工廠方法模式實現(xiàn)單例模式的功能呢屁倔?單例模式的核心要求就是在內(nèi)存中只有一個對象,通過工廠方法模式也可以只在內(nèi)存中生產(chǎn)一個對象暮胧,代碼如下所示锐借。

//單例類
public class Singleton {
    //不允許通過new產(chǎn)生一個對象
    private Singleton() {}
    public void doSomething() {
        //業(yè)務(wù)處理
    }
}

Singleton保證不能通過正常的渠道建立一個對象,那 SingletonFactory 如何建立一個單例對象呢往衷?答案是通過反射方式創(chuàng)建钞翔,如代碼清單下所示。

//負(fù)責(zé)生成單例的工廠類
pub1ic class SingletonFactory {
    private static Singleton singleton;
    static{
        try {
            Class cl = Class.forName(Singleton.class.getName());
            //獲得無參構(gòu)造
            Constructor constructor = cl.getDeclaredConstructor();
            //設(shè)置無參構(gòu)造是可訪問的
            constructor.setAccessible(true);
            //產(chǎn)生一個實例對象
            singleton = (Sing1eton) constructor.newInstance();
        } catch (Exception e) {
            //異常處理
        }
    }
    public static Singleton getSingleton() {
        return singleton;
    }
}

通過獲得類構(gòu)造器席舍,然后設(shè)置訪問權(quán)限布轿,生成一個對象,然后提供外部訪問来颤,保證內(nèi)存中的對象唯一汰扭。當(dāng)然,其他類也可以通過反射的方式建立一個單例對象福铅,確實如此萝毛,但是一個項目或團隊是有章程和規(guī)范的,何況已經(jīng)提供了一個獲得單例對象的方法滑黔,為什么還要重新創(chuàng)建一個新對象呢笆包?除非是有人作惡。
以上通過工廠方法模式創(chuàng)建了一個單例對象拷沸,該框架可以繼續(xù)擴展色查,在一個項目中可以產(chǎn)生一個單例構(gòu)造器,所有需要產(chǎn)生單例的類都遵循一定的規(guī)則(構(gòu)造方法是private)撞芍,然后通過擴展該框架,只要輸入一個類型就可以獲得唯一的一個實例跨扮。

3.2 延遲加載的工廠類

//延遲加載的工廠類
public class ProductFactory{
    //創(chuàng)建一個map對象用來存放各種對象
    private static final Map<String, Product> prMap = new HashMap();
    //創(chuàng)建對象
    public static synchronized Product createProduct(String type) throws Exception{
            //首先定義對象為空
            Product product =nu11 ;
            //如果Map中已經(jīng)有這個對象
            if(prMap.containsKey(type)) {
                //從Map中獲取在這個對象并返回
                product = prMap.get(type);
            }else{
                //如果Map中不存在這個對象序无,則匹配輸入類型,判斷創(chuàng)建什么對象
                if(type.equa1s("Product1")) {
                product = new ConcreteProduct1();
            }else{
                product = new ConcreteProduct2();
                //同時把對象放到緩存容器中
                prMap.put(type,product);
            }
            //返回所需要的對象   
            return product;
        }
}

代碼還比較簡單衡创,通過定義一個Map容器帝嗡,容納所有產(chǎn)生的對象,如果在Map容器中已經(jīng)有的對象璃氢,則直接取出返回哟玷;如果沒有,則根據(jù)需要的類型產(chǎn)生一個對象并放入到Map容器中,以防便下次調(diào)用。延遲加載框架是可以擴展的巢寡,例如限制某一個產(chǎn) 品類的最大實例化數(shù)量喉脖,可以通過判斷Map中已有的對象數(shù)量來實現(xiàn),這樣的處理是非常有意義的抑月,例如 JDBC 連接數(shù)據(jù)庫树叽,都會要求設(shè)置一個 MaxConnections 最大連接數(shù)量,該數(shù)量就是內(nèi)存中最大實例化的數(shù)量谦絮。延遲加載還可以用在對象初始化比較復(fù)雜的情況下题诵,例如硬件訪問,涉及多方面的交互层皱,則可以通過延遲加載降低對象的產(chǎn)生和銷毀帶來的復(fù)雜性性锭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市叫胖,隨后出現(xiàn)的幾起案子草冈,更是在濱河造成了極大的恐慌,老刑警劉巖臭家,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疲陕,死亡現(xiàn)場離奇詭異,居然都是意外死亡钉赁,警方通過查閱死者的電腦和手機蹄殃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來你踩,“玉大人诅岩,你說我怎么就攤上這事〈ぃ” “怎么了吩谦?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長膝藕。 經(jīng)常有香客問我式廷,道長,這世上最難降的妖魔是什么芭挽? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任滑废,我火速辦了婚禮,結(jié)果婚禮上袜爪,老公的妹妹穿的比我還像新娘蠕趁。我一直安慰自己,他們只是感情好辛馆,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布俺陋。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪腊状。 梳的紋絲不亂的頭發(fā)上诱咏,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天,我揣著相機與錄音寿酌,去河邊找鬼胰苏。 笑死,一個胖子當(dāng)著我的面吹牛醇疼,可吹牛的內(nèi)容都是我干的硕并。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼秧荆,長吁一口氣:“原來是場噩夢啊……” “哼倔毙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乙濒,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤陕赃,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后颁股,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體么库,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年甘有,在試婚紗的時候發(fā)現(xiàn)自己被綠了诉儒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡亏掀,死狀恐怖忱反,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情滤愕,我是刑警寧澤温算,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站间影,受9級特大地震影響注竿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜魂贬,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一蔓搞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧随橘,春花似錦、人聲如沸锦庸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至萝嘁,卻和暖如春梆掸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背牙言。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工酸钦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咱枉。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓卑硫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蚕断。 傳聞我的和親對象是個殘疾皇子欢伏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容