【java設計模式】之 工廠(Factory)模式


1.工廠模式的定義

        工廠模式使用的頻率非常高,我們在開發中總能見到它們的身影。其定義為:Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.即定義一個用於創建對象的接口,讓子類決定實例化哪一個類。工廠方法使一個類的實例化延遲到其子類。工廠方法模式的通用類圖如下所示:

        如圖所示,Product抽象類負責定義產品的共性,實現對事物最抽象的定義,Creator為抽象工廠類,具體如何創建產品類由具體的實現工廠ConcreteCreator來完成。我們來看一下通用的模板代碼:

public abstract class Product {  
      
    public void method() { //產品類的公共方法,已經實現  
        //實現了公共的邏輯  
    }  
      
    public abstract void method2(); //非公共方法,需要子類具體實現  
}  

具體產品類可以有多個,都繼承與抽象類Product,如下:

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

抽象工廠類負責定義產品對象的產生,代碼如下:

public abstract class Creator {  
    //創建一個產品對象,其輸入參數類型可以自行設置  
    public abstract <T extends Product> T createProduct(Class<T> clazz);  
}  

 這里用的是泛型,傳入的對象必須是Product抽象類的實現類。具體如何產生一個產品的對象,是由具體工廠類實現的,具體工廠類繼承這個抽象工廠類:

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;  
    }  
}  

通過這樣的設計,我們就可以在測試類中隨意生產產品了,看下面的測試類:

public class FactoryTest {  
  
    public static void main(String[] args) {  
        Creator factory = new ConcreteCreator();  
        Product product1 = factory.createProduct(ConcreteProduct1.class); //通過不同的類創建不同的產品  
        Product product2 = factory.createProduct(ConcreteProduct2.class);  
         /* 
          * 下面繼續其他業務處理 
          */  
     }  
}  

  下面舉個女媧造人的例子闡述一下工廠模式的實際應用。

 

2.用女蝸造人闡述工廠模式

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

        抽象接口Human是人類,里面有兩個方法,getColor獲得皮膚顏色,talk交談。下面三個具體Human的實現類,分別實現三個人種。根據工廠模式,應該有個抽象工廠,AbstractHumanFactory就擔當了這個責任,里面有個抽象方法createHuman,那HumanFactory就是實現類了,實現抽象工廠里的方法。下面我們看看具體實現:

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

  接下來對Human接口的不同實現:

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");  
    }  
}  

 好了,人的模子搞好了,現在女媧要開始搭建八卦爐了,於是女媧開始畫八卦爐模型了:

public abstract class AbstractHumanFactory{  
    public abstract <T extends Human> T createHuman(Class<T> clazz); //注意這里T必須是Human的實現類才行,因為要造Human嘛  
}  

然后女媧開始具體實現這個八卦爐了……

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("人種產生錯誤");  
        }  
        return (T) human;  
    }  
}  

 好,現在人種也有了,八卦爐也有了,剩下的就是采集黃土,然后命令八卦爐開始生產了:

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); //白人誕生了  
      }  
}  

 女媧就是這么把人造出來的……這就是工廠模式!

 

3.工廠模式的擴展

3.1 靜態工廠模式

        我們還用上面女媧造人的例子說明,現在女媧在思考一個問題:我現在只需要一個工廠就可以把人生產出來,我干嘛要具體的工廠對象呢?我只要使用靜態方法就好了。這樣一想,於是女媧就開始把AbstractHumanFactory抽象類去掉了,只保留了HumanFactory類,同時把createHuman方法設置成了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("人種產生錯誤");  
        }  
        return (T) human;  
    }  
}  

然后女媧造人的時候就不用new什么八卦爐了,直接用HumanFactory類調用就行了:

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

   這就是靜態工廠模式,在實際項目中,根據需求可以設置成靜態工廠類,但是缺點是擴展比較困難,如果就一個工廠,不需要擴展,可以這么設計,仍然是很常用的。

3.2 多個工廠模式

        我們還以女媧造人為例,后來女媧想了想,,這人不可能就說話吧,還得有不同的屬性殺的,如果在一個八卦爐里造,除了new一個人外,還得對不同的人設置不同的屬性,這樣的話,八卦爐有點吃不消阿……又有點亂啊……但是她想到了一個法子,每個人種弄個八卦爐,不同的八卦爐專門生產不同的人種,這樣就結構清晰了,她在造人的時候自己選擇與哪個八卦爐相關聯就行了。示意圖如下:

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

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

 注意抽象方法中已經不需要再傳遞相關類的參數了,因為每個具體的工廠都已經非常明確自己的職責:創建自己負責的產品類對象。所以不同的工廠實現自己的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();  
    }  
}  

  這樣三個不同的工廠就產生了,每個工廠對應只生產自己對應的人種。所以現在女媧造人就可以不用一個八卦爐了,分工協作,各不影響了!

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

 這種工廠模式的好處是職責清晰,結構簡單,但是給擴擴展性和可維護性帶來了一定的影響,因為如果要擴展一個產品類,就需要建立一個相應的工廠類,這樣就增加了擴展的難度。因為工廠類和產品類的數量是相同的,維護時也需要考慮兩個對象之間的關系。但是這種模式還是很常用的。

 

3.3 替代單例模式

        上一章介紹了單例模式,並且指出了單例和多例的一些缺點,但是我們是不是可以采用工廠模式來實現一個單例模式的功能呢?答案是肯定的,單例模式的核心要求就是在內存中只有一個對象,通過工廠方法模式也可以只在內存中生產一個對象。見下面:

Singleton類定義了一個private的無參構造方法,目的是不允許通過new的方式創建對象,另外,Singleton類也不自己定義一個Singleton對象了,因為它要通過工廠來獲得。

public class Singleton {  
    private Singleton() {  
          
    }  
    public void doSomething() {  
        //業務處理  
    }  
}  

既然Singleton不能通過正常的渠道建立一個對象,那SingletonFactory如何建立一個單例對象呢?答案是通過反射方式創建:

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

 以上通過工廠方法模式創建了一個單例對象,該框架可以繼續擴展,在一個項目中可以產生一個單例構造器,所有需要產生單例的類都遵循一定的規則(構造方法是private),然后通過擴展該框架,只要輸入一個類型就可以獲得唯一的一個實例。

 

3.4 延遲初始化

       

   何為延遲初始化(Lazy initialization)?即一個對象被使用完畢后,並不立刻釋放,工廠類保持其初始狀態,等待再次被使用。延遲初始化是工廠模式的一個擴展應用,其通用類表示如下:

        ProductFactory負責產品類對象的創建工作,並且通過prMap變量產生一個緩存,對需要再次被重用的對象保留:

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中已經有這個對象  
        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;  
    }  

  代碼比較簡單,通過定義一個Map容器,容納所有產生的對象,每次在new一個對象的時候先判斷Map中有沒有,有就不用再new了,直接取。另外,每次new過一個對象后,也將其放入Map中方便下次調用。

        延遲加載時很有用的,比如JDBC連接數據庫,會要求設置一個MaxConnections最大連接數量,該數量就是內存中最大實例化的數量。

 

4. 工廠模式的應用

    優點:

        1. 工廠模式具有良好的封裝性,代碼結構清晰,也有利於擴展。在增加產品類的情況下,只需要適當地修改具體的工廠類或擴展一個工廠類,就可以完成“擁抱變化”。

        2. 工廠模式可以屏蔽產品類。這一點非常重要,產品類的實現如何變化,調用者都不用關系,只需要關心產品的接口,只要接口保持不變,系統的上層模塊就不需要發生變化。

        3. 工廠模式是典型的解耦框架。高層模塊只需要知道產品的抽象類,其他的實現類都不用關心。

       工廠模式就介紹這么多吧,如果錯誤之處,歡迎留言指正~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM