設(shè)計(jì)模式--工廠方法模式

設(shè)計(jì)原則:

要依賴抽象,不要依賴具體類

目錄

本文的結(jié)構(gòu)如下:

  • 什么是工廠方法模式
  • 為什么要用該模式
  • 模式的結(jié)構(gòu)
  • 代碼示例
  • 優(yōu)點(diǎn)和缺點(diǎn)
  • 適用環(huán)境
  • 模式應(yīng)用
  • 模式擴(kuò)展
  • 總結(jié)

一、前言

簡(jiǎn)單工廠模式雖然簡(jiǎn)單,但存在一個(gè)很嚴(yán)重的問(wèn)題。當(dāng)系統(tǒng)中需要引入新產(chǎn)品時(shí),由于靜態(tài)工廠方法通過(guò)所傳入?yún)?shù)的不同來(lái)創(chuàng)建不同的產(chǎn)品,這必定要修改工廠類的源代碼,將違背“開(kāi)閉原則”,如何實(shí)現(xiàn)增加新產(chǎn)品而不影響已有代碼?工廠方法模式應(yīng)運(yùn)而生,本文將介紹第二種工廠模式——工廠方法模式。

二、什么是工廠方法模式

工廠方法模式(Factory Method Pattern)又稱為工廠模式,也叫虛擬構(gòu)造器(Virtual Constructor)模式或者多態(tài)工廠(Polymorphic Factory)模式,它屬于類創(chuàng)建型模式。在工廠方法模式中,工廠父類負(fù)責(zé)定義創(chuàng)建產(chǎn)品對(duì)象的公共接口,而工廠子類則負(fù)責(zé)生成具體的產(chǎn)品對(duì)象,這樣做的目的是將產(chǎn)品類的實(shí)例化操作延遲到工廠子類中完成,即通過(guò)工廠子類來(lái)確定究竟應(yīng)該實(shí)例化哪一個(gè)具體產(chǎn)品類。

三、為什么要用該模式

3.1、官方解釋

在簡(jiǎn)單工廠模式中只提供一個(gè)工廠類,該工廠類處于對(duì)產(chǎn)品類進(jìn)行實(shí)例化的中心位置,它需要知道每一個(gè)產(chǎn)品對(duì)象的創(chuàng)建細(xì)節(jié),并決定何時(shí)實(shí)例化哪一個(gè)產(chǎn)品類。簡(jiǎn)單工廠模式最大的缺點(diǎn)是當(dāng)有新產(chǎn)品要加入到系統(tǒng)中時(shí),必須修改工廠類,需要在其中加入必要的業(yè)務(wù)邏輯,這違背了“開(kāi)閉原則”。

此外,在簡(jiǎn)單工廠模式中,所有的產(chǎn)品都由同一個(gè)工廠創(chuàng)建,工廠類職責(zé)較重,業(yè)務(wù)邏輯較為復(fù)雜,具體產(chǎn)品與工廠類之間的耦合度高,嚴(yán)重影響了系統(tǒng)的靈活性和擴(kuò)展性,而工廠方法模式則可以很好地解決這一問(wèn)題。

在工廠方法模式中,不再提供一個(gè)統(tǒng)一的工廠類來(lái)創(chuàng)建所有的產(chǎn)品對(duì)象,而是針對(duì)不同的產(chǎn)品提供不同的工廠,系統(tǒng)提供一個(gè)與產(chǎn)品等級(jí)結(jié)構(gòu)對(duì)應(yīng)的工廠等級(jí)結(jié)構(gòu)。

3.2、舉個(gè)例子

還是用蛋糕店的例子說(shuō)明一下。

你的蛋糕店很火熱,每天前來(lái)買蛋糕的人絡(luò)繹不絕,碰到休息日,更是火爆到不行,排隊(duì)的人都排到了“金拱門”的門口,忙碌的你富有沖勁,決定掏開(kāi)腰包,在另一個(gè)火爆地段--一所大學(xué)的門口再開(kāi)一家分店,并且因?yàn)榇髮W(xué)旁邊學(xué)生較多,你打算做一些改良,讓新開(kāi)的分店蛋糕的口味更適合年輕人。忙碌富裕的你決定再找個(gè)程序猿來(lái)幫忙設(shè)計(jì)代碼,但你就是葛朗臺(tái),你開(kāi)的價(jià)錢太低,你只給10RMB,沒(méi)有人肯干這個(gè)活,心好的我再次被你請(qǐng)來(lái)。

你給我說(shuō)了你的想法,我心里一陣竊喜,這可以用上次get的簡(jiǎn)單工廠模式啊,于是我是這樣設(shè)計(jì)的:

/**
 * Created by w1992wishes on 2017/10/31.
 */
public class SimpleCakeFacroty {
    public static Cake createCake(String location, String type){
        Cake cake;
        if ("center".equalsIgnoreCase(location)){
            if ("cheese".equals(type)) {
                cake = new CenterCheeseCake();
            } else if ("fruit".equals(type)) {
                cake = new CenterFruitCake();
            } else if ("cream".equals(type)) {
                cake = new CenterCreamCake();
            } else {
                cake = new CenterDefaultCake();
            }
        }else if("college".equalsIgnoreCase(location)){
            if ("cheese".equals(type)) {
                cake = new CollegeCheeseCake();
            } else if ("fruit".equals(type)) {
                cake = new CollegeFruitCake();
            } else if ("cream".equals(type)) {
                cake = new CollegeCreamCake();
            } else {
                cake = new CollegeDefaultCake();
            }
        }else if("other".equalsIgnoreCase(location)){
            if ("cheese".equals(type)) {
                cake = new OtherCheeseCake();
            } else if ("fruit".equals(type)) {
                cake = new OtherFruitCake();
            } else if ("cream".equals(type)) {
                cake = new OtherCreamCake();
            } else {
                cake = new OtherDefaultCake();
            }
        }
        return cake;
    }
}

所以CakeStore是這樣的:

public class CakeStore {
    public Cake orderCake(String location, String type) {
        Cake cake;
        cake = SimpleCakeFacroty.createCake(location, type);
        cake.bake();
        cake.box();
        return cake;
    }
}

一個(gè)上午的功夫,我認(rèn)真寫出這段代碼,帶著滿滿的成就感把它交給了你,你拍了拍手上的面粉,結(jié)果后只瞥了一眼,就噴著口水對(duì)我說(shuō):“你寫的代碼就是一堆狗屎?!?/p>

沒(méi)有任何猶豫,你再次辭退了我,但你的職業(yè)精神我很敬佩,在我垂頭喪氣離開(kāi)之前,你沒(méi)有給我10RMB的報(bào)酬,而是咬著牙對(duì)我說(shuō):

雖然你用了簡(jiǎn)單工廠模式,實(shí)現(xiàn)了蛋糕的創(chuàng)建和消費(fèi)分離,但是這里卻存在嚴(yán)重問(wèn)題:

  1. 大量的if...else...相互嵌套,邏輯復(fù)雜,代碼不直觀,導(dǎo)致維護(hù)和測(cè)試都很困難,這真是最糟糕的代碼;
  2. 擴(kuò)展不靈活,必須修改靜態(tài)工廠方法的業(yè)務(wù)邏輯,違反了“開(kāi)閉原則”。
  3. 工廠方法和具體的蛋糕類嚴(yán)重耦合,而且具體的蛋糕類特別多,嚴(yán)重違背了“要依賴抽象,不要依賴具體”的設(shè)計(jì)原則。
  4. ......

怎么解決這個(gè)問(wèn)題呢?工廠方法模式正好合適。具體代碼呢?先等介紹完工廠方法模式的結(jié)構(gòu)再看啦。

四、模式的結(jié)構(gòu)

在工廠方法模式結(jié)構(gòu)圖中包含如下幾個(gè)角色:

  • Factory(抽象工廠類):在抽象工廠類中,聲明了工廠方法(Factory Method),用于返回一個(gè)產(chǎn)品。抽象工廠是工廠方法模式的核心,所有創(chuàng)建對(duì)象的工廠類都必須實(shí)現(xiàn)該接口。
  • ConcreteFactory(具體工廠類):它是抽象工廠類的子類,實(shí)現(xiàn)了抽象工廠中定義的工廠方法,并可由客戶端調(diào)用,返回一個(gè)具體產(chǎn)品類的實(shí)例。
  • Product(抽象產(chǎn)品類):它是定義產(chǎn)品的接口,是工廠方法模式所創(chuàng)建對(duì)象的超類型,也就是產(chǎn)品對(duì)象的公共父類。
  • (ConcreteProduct具體產(chǎn)品類):它實(shí)現(xiàn)了抽象產(chǎn)品接口,某種類型的具體產(chǎn)品由專門的具體工廠創(chuàng)建,具體工廠和具體產(chǎn)品之間一一對(duì)應(yīng)。

與簡(jiǎn)單工廠模式相比,工廠方法模式最重要的區(qū)別是引入了抽象工廠角色,抽象工廠可以是接口,也可以是抽象類或者具體類。

五、代碼示例

首先定義一個(gè)抽象工廠,這個(gè)抽象工廠有一個(gè)抽象方法用于生產(chǎn)具體產(chǎn)品:

/**
 * Created by w1992wishes on 2017/11/01.
 */
public abstract class CakeStore {
    public Cake orderCake(String type) {
        Cake cake;
        cake = createCake(type);
        cake.bake();
        cake.box();
        return cake;
    }

    protected abstract Cake createCake(String type);
}

在抽象工廠中聲明了工廠方法但并未實(shí)現(xiàn)工廠方法,具體產(chǎn)品對(duì)象的創(chuàng)建由其子類負(fù)責(zé),客戶端針對(duì)抽象工廠編程,可在運(yùn)行時(shí)再指定具體工廠類,具體工廠類實(shí)現(xiàn)了工廠方法,不同的具體工廠可以創(chuàng)建不同的具體產(chǎn)品。

/**
 * Created by w1992wishes on 2017/11/1.
 */
public class CenterCakeStore extends CakeStore {
    @Override
    protected Cake createCake(String type) {
        Cake cake = null;
        if ("cheese".equals(type)) {
            cake = new CenterCheeseCake();
        } else if ("fruit".equals(type)) {
            cake = new CenterFruitCake();
        } else if ("cream".equals(type)) {
            cake = new CenterCreamCake();
        }
        return cake;
    }
}
/**
 * Created by w1992wishes on 2017/11/1.
 */
public class CollegeCakeStore extends CakeStore {
    @Override
    protected Cake createCake(String type) {
        Cake cake = null;
        if ("cheese".equals(type)) {
            cake = new CollegeCheeseCake();
        } else if ("fruit".equals(type)) {
            cake = new CollegeFruitCake();
        } else if ("cream".equals(type)) {
            cake = new CollegeCreamCake();
        }
        return cake;
    }
}

抽象產(chǎn)品:

/**
 * Created by w1992wishes on 2017/11/01.
 */
public abstract class Cake {
    void prepare(){
        System.out.println("step 1......");
        System.out.println("step 2......");
        System.out.println("step 3......");
        System.out.println("step 4......");
    }
    void bake(){
        System.out.println("bake");
    }
    void box(){
        System.out.println("box");
    }
}

具體產(chǎn)品有自己獨(dú)有的bake(),box()方法:

 * Created by w1992wishes on 2017/10/31.
 */
public class CenterCheeseCake extends Cake {
    public CenterCheeseCake(){
        name = "center cheese cake";
    }
    @Override
    public void bake(){
        System.out.println("不用烘箱,我要用火烤!");
    }
}
/**
 * Created by w1992wishes on 2017/11/1.
 */
public class CollegeFruitCake extends Cake {
    public CollegeFruitCake(){
        name = "center fruit cake";
    }
    @Override
    public void box(){
        System.out.println("不用圓盒子打包,我愛(ài)國(guó),用五角星盒子!");
    }
}

最后客戶端:

public class Client {
    public static void main(String[] args) {
        //這里可通過(guò)引入配置文件更改
        CakeStore cakeStore = new CenterCakeStore();
        Cake cake = cakeStore.orderCake("cheese");
    }
}

這樣一改,往后想再新開(kāi)一個(gè)蛋糕店,只需繼承自CakeStore新增生產(chǎn)具體的Cake,而不需改動(dòng)源代碼,這樣就符合了“開(kāi)閉原則”;而且客戶端更換蛋糕店可以通過(guò)配置來(lái)完成,同樣不需修改源代碼。

六、優(yōu)點(diǎn)和缺點(diǎn)

6.1、優(yōu)點(diǎn)

  • 在工廠方法模式中,工廠方法用來(lái)創(chuàng)建客戶端所需要的產(chǎn)品,同時(shí)還向客戶端隱藏了哪種具體產(chǎn)品類將被實(shí)例化這一細(xì)節(jié),客戶端只需要關(guān)心所需產(chǎn)品對(duì)應(yīng)的工廠,無(wú)須關(guān)心創(chuàng)建細(xì)節(jié),甚至無(wú)須知道具體產(chǎn)品類的類名。
  • 基于工廠角色和產(chǎn)品角色的多態(tài)性設(shè)計(jì)是工廠方法模式的關(guān)鍵。它能夠使工廠可以自主確定創(chuàng)建何種產(chǎn)品對(duì)象,而如何創(chuàng)建這個(gè)對(duì)象的細(xì)節(jié)則完全封裝在具體工廠內(nèi)部。工廠方法模式之所以又被稱為多態(tài)工廠模式,是因?yàn)樗械木唧w工廠類都具有同一抽象父類。
  • 使用工廠方法模式的另一個(gè)優(yōu)點(diǎn)是在系統(tǒng)中加入新產(chǎn)品時(shí),無(wú)須修改抽象工廠和抽象產(chǎn)品提供的接口,無(wú)須修改客戶端,也無(wú)須修改其他的具體工廠和具體產(chǎn)品,而只要添加一個(gè)具體工廠和具體產(chǎn)品就可以了。這樣,系統(tǒng)的可擴(kuò)展性也就變得非常好,完全符合“開(kāi)閉原則”。

6.2、缺點(diǎn)

  • 在添加新產(chǎn)品時(shí),需要編寫新的具體產(chǎn)品類,而且還要提供與之對(duì)應(yīng)的具體工廠類,系統(tǒng)中類的個(gè)數(shù)將成對(duì)增加,在一定程度上增加了系統(tǒng)的復(fù)雜度,有更多的類需要編譯和運(yùn)行,會(huì)給系統(tǒng)帶來(lái)一些額外的開(kāi)銷。
  • 由于考慮到系統(tǒng)的可擴(kuò)展性,需要引入抽象層,在客戶端代碼中均使用抽象層進(jìn)行定義,增加了系統(tǒng)的抽象性和理解難度,且在實(shí)現(xiàn)時(shí)可能需要用到DOM、反射等技術(shù),增加了系統(tǒng)的實(shí)現(xiàn)難度。

七、適用環(huán)境

在以下情況下可以使用工廠方法模式:

  • 一個(gè)類不知道它所需要的對(duì)象的類:在工廠方法模式中,客戶端不需要知道具體產(chǎn)品類的類名,只需要知道所對(duì)應(yīng)的工廠即可,具體的產(chǎn)品對(duì)象由具體工廠類創(chuàng)建;客戶端需要知道創(chuàng)建具體產(chǎn)品的工廠類。
  • 一個(gè)類通過(guò)其子類來(lái)指定創(chuàng)建哪個(gè)對(duì)象:在工廠方法模式中,對(duì)于抽象工廠類只需要提供一個(gè)創(chuàng)建產(chǎn)品的接口,而由其子類來(lái)確定具體要?jiǎng)?chuàng)建的對(duì)象,利用面向?qū)ο蟮亩鄳B(tài)性和里氏代換原則,在程序運(yùn)行時(shí),子類對(duì)象將覆蓋父類對(duì)象,從而使得系統(tǒng)更容易擴(kuò)展。
  • 將創(chuàng)建對(duì)象的任務(wù)委托給多個(gè)工廠子類中的某一個(gè),客戶端在使用時(shí)可以無(wú)須關(guān)心是哪一個(gè)工廠子類創(chuàng)建產(chǎn)品子類,需要時(shí)再動(dòng)態(tài)指定,可將具體工廠類的類名存儲(chǔ)在配置文件或數(shù)據(jù)庫(kù)中。

八、模式應(yīng)用

JDBC中的工廠方法:

Connection conn=DriverManager.getConnection("jdbc:microsoft:sqlserver://loc
alhost:1433; DatabaseName=DB;user=sa;password=");
Statement statement=conn.createStatement();
ResultSet rs=statement.executeQuery("select * from UserInfo");

九、模式擴(kuò)展

  • 使用多個(gè)工廠方法:在抽象工廠角色中可以定義多個(gè)工廠方法,從而使具體工廠角色實(shí)現(xiàn)這些不同的工廠方法,這些方法可以包含不同的業(yè)務(wù)邏輯,以滿足對(duì)不同的產(chǎn)品對(duì)象的需求。
  • 產(chǎn)品對(duì)象的重復(fù)使用:工廠對(duì)象將已經(jīng)創(chuàng)建過(guò)的產(chǎn)品保存到一個(gè)集合(如數(shù)組、List等)中,然后根據(jù)客戶對(duì)產(chǎn)品的請(qǐng)求,對(duì)集合進(jìn)行查詢。如果有滿足要求的產(chǎn)品對(duì)象,就直接將該產(chǎn)品返回客戶端;如果集合中沒(méi)有這樣的產(chǎn)品對(duì)象,那么就創(chuàng)建一個(gè)新的滿足要求的產(chǎn)品對(duì)象,然后將這個(gè)對(duì)象在增加到集合中,再返回給客戶端。
  • 多態(tài)性的喪失和模式的退化:如果工廠僅僅返回一個(gè)具體產(chǎn)品對(duì)象,便違背了工廠方法的用意,發(fā)生退化,此時(shí)就不再是工廠方法模式了。一般來(lái)說(shuō),工廠對(duì)象應(yīng)當(dāng)有一個(gè)抽象的父類型,如果工廠等級(jí)結(jié)構(gòu)中只有一個(gè)具體工廠類的話,抽象工廠就可以省略,也將發(fā)生了退化。當(dāng)只有一個(gè)具體工廠,在具體工廠中可以創(chuàng)建所有的產(chǎn)品對(duì)象,并且工廠方法設(shè)計(jì)為靜態(tài)方法時(shí),工廠方法模式就退化成簡(jiǎn)單工廠模式。

十、總結(jié)

  • 工廠方法模式又稱為工廠模式,它屬于類創(chuàng)建型模式。在工廠方法模式中,工廠父類負(fù)責(zé)定義創(chuàng)建產(chǎn)品對(duì)象的公共接口,而工廠子類則負(fù)責(zé)生成具體的產(chǎn)品對(duì)象,這樣做的目的是將產(chǎn)品類的實(shí)例化操作延遲到工廠子類中完成,即通過(guò)工廠子類來(lái)確定究竟應(yīng)該實(shí)例化哪一個(gè)具體產(chǎn)品類。
  • 工廠方法模式包含四個(gè)角色:抽象產(chǎn)品是定義產(chǎn)品的接口,是工廠方法模式所創(chuàng)建對(duì)象的超類型,即產(chǎn)品對(duì)象的共同父類或接口;具體產(chǎn)品實(shí)現(xiàn)了抽象產(chǎn)品接口,某種類型的具體產(chǎn)品由專門的具體工廠創(chuàng)建,它們之間往往一一對(duì)應(yīng);抽象工廠中聲明了工廠方法,用于返回一個(gè)產(chǎn)品,它是工廠方法模式的核心,任何在模式中創(chuàng)建對(duì)象的工廠類都必須實(shí)現(xiàn)該接口;具體工廠是抽象工廠類的子類,實(shí)現(xiàn)了抽象工廠中定義的工廠方法,并可由客戶調(diào)用,返回一個(gè)具體產(chǎn)品類的實(shí)例。
  • 工廠方法模式是簡(jiǎn)單工廠模式的進(jìn)一步抽象和推廣。由于使用了面向?qū)ο蟮亩鄳B(tài)性,工廠方法模式保持了簡(jiǎn)單工廠模式的優(yōu)點(diǎn),而且克服了它的缺點(diǎn)。在工廠方法模式中,核心的工廠類不再負(fù)責(zé)所有產(chǎn)品的創(chuàng)建,而是將具體創(chuàng)建工作交給子類去做。這個(gè)核心類僅僅負(fù)責(zé)給出具體工廠必須實(shí)現(xiàn)的接口,而不負(fù)責(zé)產(chǎn)品類被實(shí)例化這種細(xì)節(jié),這使得工廠方法模式可以允許系統(tǒng)在不修改工廠角色的情況下引進(jìn)新產(chǎn)品。
  • 工廠方法模式的主要優(yōu)點(diǎn)是增加新的產(chǎn)品類時(shí)無(wú)須修改現(xiàn)有系統(tǒng),并封裝了產(chǎn)品對(duì)象的創(chuàng)建細(xì)節(jié),系統(tǒng)具有良好的靈活性和可擴(kuò)展性;其缺點(diǎn)在于增加新產(chǎn)品的同時(shí)需要增加新的工廠,導(dǎo)致系統(tǒng)類的個(gè)數(shù)成對(duì)增加,在一定程度上增加了系統(tǒng)的復(fù)雜性。
  • 工廠方法模式適用情況包括:一個(gè)類不知道它所需要的對(duì)象的類;一個(gè)類通過(guò)其子類來(lái)指定創(chuàng)建哪個(gè)對(duì)象;將創(chuàng)建對(duì)象的任務(wù)委托給多個(gè)工廠子類中的某一個(gè),客戶端在使用時(shí)可以無(wú)須關(guān)心是哪一個(gè)工廠子類創(chuàng)建產(chǎn)品子類,需要時(shí)再動(dòng)態(tài)指定。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 工廠方法模式 介紹 簡(jiǎn)單工廠模式雖然簡(jiǎn)單,并且實(shí)現(xiàn)了創(chuàng)建和使用分離的功能,但存在一個(gè)很嚴(yán)重的問(wèn)題。當(dāng)系統(tǒng)中需要引入...
    666真666閱讀 319評(píng)論 0 1
  • 設(shè)計(jì)模式匯總 一、基礎(chǔ)知識(shí) 1. 設(shè)計(jì)模式概述 定義:設(shè)計(jì)模式(Design Pattern)是一套被反復(fù)使用、多...
    MinoyJet閱讀 4,098評(píng)論 1 15
  • 1 場(chǎng)景問(wèn)題# 1.1 導(dǎo)出數(shù)據(jù)的應(yīng)用框架## 考慮這樣一個(gè)實(shí)際應(yīng)用:實(shí)現(xiàn)一個(gè)導(dǎo)出數(shù)據(jù)的應(yīng)用框架,來(lái)讓客戶選擇數(shù)據(jù)...
    七寸知架構(gòu)閱讀 7,025評(píng)論 6 75
  • 這一篇博文來(lái)介紹工廠模式中的第二種工廠方法模式。 介紹 工廠方法模式(Factory Method Pattern...
    東西的南北閱讀 347評(píng)論 0 1
  • 一直使用糊涂賬記賬,感觸良多 不記不知道,以及嚇一跳,我的恩格爾系數(shù)超過(guò)了百分之五十,居于各類消費(fèi)之首,每個(gè)月大概...
    甲子榮榮閱讀 306評(píng)論 0 0

友情鏈接更多精彩內(nèi)容