工廠方法模式

工廠模式的定義

工廠模式使用的頻率非常高,我們?cè)陂_(kāi)發(fā)中總能見(jiàn)到它們的身影。其定義為:Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

即定義一個(gè)用于創(chuàng)建對(duì)象的接口,讓子類決定實(shí)例化哪一個(gè)類。工廠方法使一個(gè)類的實(shí)例化延遲到其子類。

工廠方法模式的通用類圖如下所示:

工廠方法模式通用類圖

如圖所示,Product抽象類負(fù)責(zé)定義產(chǎn)品的共性,實(shí)現(xiàn)對(duì)事物最抽象的定義,Creator為抽象工廠類,具體如何創(chuàng)建產(chǎn)品類由具體的實(shí)現(xiàn)工廠ConcreteCreator來(lái)完成。我們來(lái)看一下通用的模板代碼:

public abstract class Product {
    public void method() { //產(chǎn)品類的公共方法,已經(jīng)實(shí)現(xiàn)
        //實(shí)現(xiàn)了公共的邏輯
    }
    public abstract void method2(); //非公共方法,需要子類具體實(shí)現(xiàn)
}

具體產(chǎn)品類可以有多個(gè),都繼承與抽象類Product,如下:

public class ConcreateProduct1 extends Product {
    @Override
    public void method2() {
        //product1的業(yè)務(wù)邏輯
    }
}
public class ConcreateProduct2 extends Product {
    @Override
    public void method2() {
        //product2的業(yè)務(wù)邏輯
    }
}

抽象工廠類負(fù)責(zé)定義產(chǎn)品對(duì)象的產(chǎn)生,代碼如下:

public abstract class Creator {
    //創(chuàng)建一個(gè)產(chǎn)品對(duì)象,其輸入?yún)?shù)類型可以自行設(shè)置
    public abstract <T extends Product> T createProduct(Class<T> clazz);
}

這里用的是泛型,傳入的對(duì)象必須是Product抽象類的實(shí)現(xiàn)類。具體如何產(chǎn)生一個(gè)產(chǎn)品的對(duì)象,是由具體工廠類實(shí)現(xiàn)的,具體工廠類繼承這個(gè)抽象工廠類:

public class ConcreteCreator extends Creator {
 
    @Override
    public <T extends Product> T createProduct(Class<T> clazz) {
        Product product = null;
        try {
            product = (Product) Class.forName(clazz.getName()).newInstance();
        } catch (Exception e) { //異常處理
            e.printStackTrace();
        }
        return (T) product;
    }
}

通過(guò)這樣的設(shè)計(jì),我們就可以在測(cè)試類中隨意生產(chǎn)產(chǎn)品了,看下面的測(cè)試類:

public class FactoryTest {
 
    public static void main(String[] args) {
        Creator factory = new ConcreteCreator();
        Product product1 = factory.createProduct(ConcreteProduct1.class); //通過(guò)不同的類創(chuàng)建不同的產(chǎn)品
        Product product2 = factory.createProduct(ConcreteProduct2.class);
         /*
          * 下面繼續(xù)其他業(yè)務(wù)處理
          */
     }
}

下面舉個(gè)女媧造人的例子闡述一下工廠模式的實(shí)際應(yīng)用。

用女蝸造人闡述工廠模式

現(xiàn)在女媧要造人,她要造三種人:白種人、黃種人和黑種人。怎么造呢?她得有個(gè)能產(chǎn)生人類的工廠吧(類似于八卦爐的東西),這個(gè)工廠得讓她生產(chǎn)出不同的人種。每個(gè)人都有兩個(gè)屬性:皮膚顏色和說(shuō)話。那現(xiàn)在我們開(kāi)始設(shè)計(jì)女蝸造人的程序,首先我們看一下造人的類圖:

女媧造人類圖

抽象接口Human是人類,里面有兩個(gè)方法,getColor獲得皮膚顏色,talk交談。下面三個(gè)具體Human的實(shí)現(xiàn)類,分別實(shí)現(xiàn)三個(gè)人種。根據(jù)工廠模式,應(yīng)該有個(gè)抽象工廠,AbstractHumanFactory就擔(dān)當(dāng)了這個(gè)責(zé)任,里面有個(gè)抽象方法createHuman,那HumanFactory就是實(shí)現(xiàn)類了,實(shí)現(xiàn)抽象工廠里的方法。下面我們看看具體實(shí)現(xiàn):

public interface Human {    
    public void getColor();//人有不同的顏色    
    public void talk(); //人會(huì)說(shuō)話
}

接下來(lái)對(duì)Human接口的不同實(shí)現(xiàn):

public class BlackHuman implements Human {// 黑種人
 
    @Override
    public void getColor() {
        System.out.println("Black");
    }
    @Override
    public void talk() {
        System.out.println("Black man");
    }
}
public class Human implements Human {   //黃種人
 
    @Override
    public void getColor() {
        System.out.println("Yellow");
    }
    @Override
    public void talk() {
        System.out.println("Yellow man");
    }
}
public class WhiteHuman implements Human {//白種人
 
    @Override
    public void getColor() {
        System.out.println("White");
    }
    @Override
    public void talk() {
        System.out.println("White man");
    }
}

好了,人的模子搞好了,現(xiàn)在女媧要開(kāi)始搭建八卦爐了,于是女媧開(kāi)始畫八卦爐模型了:

public abstract class AbstractHumanFactory{
    public abstract <T extends Human> T createHuman(Class<T> clazz); //注意這里T必須是Human的實(shí)現(xiàn)類才行,因?yàn)橐霩uman嘛
}

然后女媧開(kāi)始具體實(shí)現(xiàn)這個(gè)八卦爐了……

public class HumanFactory extends AbstractHumanFactory {
 
    @Override
    public <T extends Human> T createHuman(Class<T> clazz) {
        Human human = null;
        try {
            human = (Product) Class.forName(clazz.getName()).newInstance();
        } catch (Exception e) { //異常處理
            System.out.println("人種產(chǎn)生錯(cuò)誤");
        }
        return (T) human;
    }
}

好,現(xiàn)在人種也有了,八卦爐也有了,剩下的就是采集黃土,然后命令八卦爐開(kāi)始生產(chǎn)了:

public class FactoryTest {
    public static void main(String[] args) {
        AbstractHumanFactory bagualu = new HunmanFactory();
        Human blackMan = bagualu.createHuman(BlackHuman.class); //黑人誕生了
        Human yellowMan = bagualu.createHuman(YelloHuman.class); //黃人誕生了
        Human whiteMan = bagualu.createHuman(WhiteHuman.class); //白人誕生了
      }
}

女媧就是這么把人造出來(lái)的……這就是工廠模式!

工廠模式的擴(kuò)展

靜態(tài)工廠模式

我們還用上面女媧造人的例子說(shuō)明,現(xiàn)在女媧在思考一個(gè)問(wèn)題:我現(xiàn)在只需要一個(gè)工廠就可以把人生產(chǎn)出來(lái),我干嘛要具體的工廠對(duì)象呢?我只要使用靜態(tài)方法就好了。這樣一想,于是女媧就開(kāi)始把AbstractHumanFactory抽象類去掉了,只保留了HumanFactory類,同時(shí)把createHuman方法設(shè)置成了static類型,搞定!

public class HumanFactory {
 
    @Override
    public static <T extends Human> T createHuman(Class<T> clazz) {
        Human human = null;
        try {
            human = (Product) Class.forName(clazz.getName()).newInstance();
        } catch (Exception e) { //異常處理
            System.out.println("人種產(chǎn)生錯(cuò)誤");
        }
        return (T) human;
    }
}

然后女媧造人的時(shí)候就不用new什么八卦爐了,直接用HumanFactory類調(diào)用就行了:

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

這就是靜態(tài)工廠模式,在實(shí)際項(xiàng)目中,根據(jù)需求可以設(shè)置成靜態(tài)工廠類,但是缺點(diǎn)是擴(kuò)展比較困難,如果就一個(gè)工廠,不需要擴(kuò)展,可以這么設(shè)計(jì),仍然是很常用的。

多個(gè)工廠模式

我們還以女媧造人為例,后來(lái)女媧想了想,,這人不可能就說(shuō)話吧,還得有不同的屬性殺的,如果在一個(gè)八卦爐里造,除了new一個(gè)人外,還得對(duì)不同的人設(shè)置不同的屬性,這樣的話,八卦爐有點(diǎn)吃不消阿……又有點(diǎn)亂啊……但是她想到了一個(gè)法子,每個(gè)人種弄個(gè)八卦爐,不同的八卦爐專門生產(chǎn)不同的人種,這樣就結(jié)構(gòu)清晰了,她在造人的時(shí)候自己選擇與哪個(gè)八卦爐相關(guān)聯(lián)就行了。示意圖如下:

多個(gè)工廠模式類圖

這樣的話AbstractHumanFactory抽象類我們就要改寫了:

public abstract class AbstractHumanFactory {
    public abstract Human createHuman();
}

注意抽象方法中已經(jīng)不需要再傳遞相關(guān)類的參數(shù)了,因?yàn)槊總€(gè)具體的工廠都已經(jīng)非常明確自己的職責(zé):創(chuàng)建自己負(fù)責(zé)的產(chǎn)品類對(duì)象。所以不同的工廠實(shí)現(xiàn)自己的createHuman方法即可:

public class BlackHumanFactory extends AbstractHumanFactory {
    public Human createHuman() {
        return new BlackHuman();
    }
}
public class YellowHumanFactory extends AbstractHumanFactory {
    public Human createHuman() {
        return new YellowHuman();
    }
}
public class WhiteHumanFactory extends AbstractHumanFactory {
    public Human createHuman() {
        return new WhiteHuman();
    }
}

這樣三個(gè)不同的工廠就產(chǎn)生了,每個(gè)工廠對(duì)應(yīng)只生產(chǎn)自己對(duì)應(yīng)的人種。所以現(xiàn)在女媧造人就可以不用一個(gè)八卦爐了,分工協(xié)作,各不影響了!

public class FactoryTest {
    public static void main(String[] args) {
        Human blackMan = new BlackHumanFactory().createHuman(); //黑人誕生了
        Human yellowMan = new YellowHumanFactory().createHuman(); //黃人誕生了
        Human whiteMan = new WhiteHumanFactory().createHuman(); //白人誕生了
      }
}

這種工廠模式的好處是職責(zé)清晰,結(jié)構(gòu)簡(jiǎn)單,但是給擴(kuò)擴(kuò)展性和可維護(hù)性帶來(lái)了一定的影響,因?yàn)槿绻獢U(kuò)展一個(gè)產(chǎn)品類,就需要建立一個(gè)相應(yīng)的工廠類,這樣就增加了擴(kuò)展的難度。因?yàn)楣S類和產(chǎn)品類的數(shù)量是相同的,維護(hù)時(shí)也需要考慮兩個(gè)對(duì)象之間的關(guān)系。但是這種模式還是很常用的。

替代單例模式

上一章介紹了單例模式,并且指出了單例和多例的一些缺點(diǎn),但是我們是不是可以采用工廠模式來(lái)實(shí)現(xiàn)一個(gè)單例模式的功能呢?答案是肯定的,單例模式的核心要求就是在內(nèi)存中只有一個(gè)對(duì)象,通過(guò)工廠方法模式也可以只在內(nèi)存中生產(chǎn)一個(gè)對(duì)象。見(jiàn)下面:

Singleton類定義了一個(gè)private的無(wú)參構(gòu)造方法,目的是不允許通過(guò)new的方式創(chuàng)建對(duì)象,另外,Singleton類也不自己定義一個(gè)Singleton對(duì)象了,因?yàn)樗ㄟ^(guò)工廠來(lái)獲得。

public class Singleton {
    private Singleton() {
        
    }
    public void doSomething() {
        //業(yè)務(wù)處理
    }
}

既然Singleton不能通過(guò)正常的渠道建立一個(gè)對(duì)象,那SingletonFactory如何建立一個(gè)單例對(duì)象呢?答案是通過(guò)反射方式創(chuàng)建:

public class SingletonFactory {
    private static Singleton instance;
    static {
        try {       
            Class clazz = Class.forName(Singleton.class.getName());
            //獲取無(wú)參構(gòu)造方法
            Constructor constructor = clazz.getDeclaredConstructor();
            //設(shè)置無(wú)參構(gòu)造方法可訪問(wèn)
            constructor.setAccessible(true);
            //產(chǎn)生一個(gè)實(shí)例對(duì)象
            instance = (Singleton) constructor.newInstance();
        } catch (Exception e) {
            //異常處理
        }
    }
    public static Singleton getInstance() {
        return instance;
    }
}

以上通過(guò)工廠方法模式創(chuàng)建了一個(gè)單例對(duì)象,該框架可以繼續(xù)擴(kuò)展,在一個(gè)項(xiàng)目中可以產(chǎn)生一個(gè)單例構(gòu)造器,所有需要產(chǎn)生單例的類都遵循一定的規(guī)則(構(gòu)造方法是private),然后通過(guò)擴(kuò)展該框架,只要輸入一個(gè)類型就可以獲得唯一的一個(gè)實(shí)例。

延遲初始化

何為延遲初始化(Lazy initialization)?即一個(gè)對(duì)象被使用完畢后,并不立刻釋放,工廠類保持其初始狀態(tài),等待再次被使用。延遲初始化是工廠模式的一個(gè)擴(kuò)展應(yīng)用,其通用類表示如下:

延遲初始化類圖

ProductFactory負(fù)責(zé)產(chǎn)品類對(duì)象的創(chuàng)建工作,并且通過(guò)prMap變量產(chǎn)生一個(gè)緩存,對(duì)需要再次被重用的對(duì)象保留:

public class ProductFactory {
    private static final Map<String, Product> prMap = new HashMap();
    public static synchronized Product createProduct(String type) throws Exception {
        Product product = null;
        //如果Map中已經(jīng)有這個(gè)對(duì)象
        if(prMap.containsKey(type)) {
            product = prMap.get(type);
        } else {
            if(type.equals("Product1")) {
                product = new ConcreteProduct1();
            }
            else {
                product = new ConcreteProduct2();
            }
            prMap.put(type, product);
        }
        return product;
    }
}

代碼比較簡(jiǎn)單,通過(guò)定義一個(gè)Map容器,容納所有產(chǎn)生的對(duì)象,每次在new一個(gè)對(duì)象的時(shí)候先判斷Map中有沒(méi)有,有就不用再new了,直接取。另外,每次new過(guò)一個(gè)對(duì)象后,也將其放入Map中方便下次調(diào)用。

延遲加載時(shí)很有用的,比如JDBC連接數(shù)據(jù)庫(kù),會(huì)要求設(shè)置一個(gè)MaxConnections最大連接數(shù)量,該數(shù)量就是內(nèi)存中最大實(shí)例化的數(shù)量。

工廠模式的應(yīng)用

優(yōu)點(diǎn):

  1. 工廠模式具有良好的封裝性,代碼結(jié)構(gòu)清晰,也有利于擴(kuò)展。在增加產(chǎn)品類的情況下,只需要適當(dāng)?shù)匦薷木唧w的工廠類或擴(kuò)展一個(gè)工廠類,就可以完成“擁抱變化”。
  2. 工廠模式可以屏蔽產(chǎn)品類。這一點(diǎn)非常重要,產(chǎn)品類的實(shí)現(xiàn)如何變化,調(diào)用者都不用關(guān)系,只需要關(guān)心產(chǎn)品的接口,只要接口保持不變,系統(tǒng)的上層模塊就不需要發(fā)生變化。
  3. 工廠模式是典型的解耦框架。高層模塊只需要知道產(chǎn)品的抽象類,其他的實(shí)現(xiàn)類都不用關(guān)心。

高層模塊只需要知道產(chǎn)品的抽象類,其他產(chǎn)品都不用關(guān)心,符合迪米特法則(最少知識(shí)原則),我不需要的就不要去交流;也符合依賴倒置原則,只依賴產(chǎn)品類的抽象;當(dāng)然也符合里式替換原則,使用產(chǎn)品子類替代產(chǎn)品父類沒(méi)有問(wèn)題。

最后編輯于
?著作權(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)容

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