創建多個“產品”的方式——工廠方法模式總結


簡單工廠模式嚴格來說不算一個設計模式——缺點

繼續說之前提到的簡單工廠模式,嚴格來說,這不算一個設計模式……而且在業務量比較多了之后,它也有諸多的隱患

1、由於工廠類集中了所有實例的創建邏輯,這就直接導致一旦這個工廠出了問題,所有的客戶端都會受到牽連

2、由於簡單工廠模式的產品基於一個共同的抽象類或者接口,這樣一來,產品的種類增加的時候,即有不同的產品接口或者抽象類的時候,簡單工廠類就需要維護大量的if-else判斷,比如導入導出功能,雖然可以用反射或者配置文件,注解等解耦,但是還有更好的辦法。

不使用工廠方法模式的例子

還是舉那個水果的例子:之前水果園子里只有蘋果和香蕉兩種水果,現在又新加一個梨的品種。簡單工廠類的代碼如下:

// 簡單工廠類
public class FruitFactoryA {
    public static FruitA getFruit(String type) {
        if ("apple".equalsIgnoreCase(type)) {
            return new AppleA();
        } else if ("banana".equalsIgnoreCase(type)) {
            return new BananaA();
        } else if ("pear".equalsIgnoreCase(type)) {
            return new PearA();
        } else {
            System.out.print("error!");
        }

        return null;
    }
}

該例子使用簡單工廠模式,工廠類過於臃腫。

public class Main {
    public static void main(String[] args) {
        FruitA apple = FruitFactoryA.getFruit("apple");
        FruitA banana = FruitFactoryA.getFruit("banana");
        FruitA pear = FruitFactoryA.getFruit("pear");

        // 不太好,沒有檢測null異常,演示
        apple.get();
        banana.get();
        pear.get();
    }
}

因為簡單工廠模式只有一個工廠類,它需要處理所有的對象創建的邏輯。如果日后需求不斷增加,則后果不堪設想,違背了單一職責,導致系統喪失靈活性和可維護性。而且更重要的是,簡單工廠模式違背了OCP——開放封閉原則。當然還有單一職責原則(雖然這里沒具體體現)。那么如何改進呢?

引入工廠方法模式——讓子類去創建對象

如何才能實現工廠類的代碼不去修改,而開放擴展——可以把工廠的職責抽象,抽象if-else判斷邏輯,把每一個判斷都抽象為一個工廠類,而這些具體的工廠類統一抽象為一個接口來約束具體子類去生產產品,這就是傳說中的——工廠方法模式,它同樣屬於類的創建型模式,又被稱為多態工廠模式。

工廠方法模式的意義是定義一個創建產品對象的工廠接口,將實際創建工作推遲到子類當中。核心工廠類不再負責產品的創建,這樣核心類成為一個抽象工廠角色,僅負責具體工廠子類必須實現的接口(僅僅起到一個約束生產動作的作用),這樣進一步抽象化的好處是使得工廠方法模式可以使系統在不修改具體工廠角色的情況下引進新的產品。

工廠方法模式實現

抽象工廠角色:工廠方法模式的核心,任何工廠類都必須實現這個接口

public interface FactoryA {
    FruitA getFruit();
}

具體工廠( Concrete  Creator)角色;具體工廠類是抽象工廠的一個實現,負責實例化產品對象

public class AppleFactoryA implements FactoryA {
    @Override
    public FruitA getFruit() {
        return new AppleA();
    }
}

public class BananaFactoryA implements FactoryA {
    @Override
    public FruitA getFruit() {
        return new BananaA();
    }
}

public class PearFactory implements FactoryA {
    @Override
    public FruitA getFruit() {
        return new PearA();
    }
}

抽象產品(Product)角色;工廠方法模式所創建的所有對象的父類,它負責描述所有實例所共有的公共接口

public interface FruitA {
    void get();
}

具體產品(Concrete Product)角色;工廠方法模式所創建的具體實例對象

public class AppleA implements FruitA {
    @Override
    public void get() {
        System.out.println("蘋果");
    }
}

public class BananaA implements FruitA {
    @Override
    public void get() {
        System.out.println("香蕉");
    }
}

public class PearA implements FruitA {
    @Override
    public void get() {
        System.out.println("梨");
    }
}

客戶端調用

public class Main {
    public static void main(String[] args) {
        // 得到對應的水果的工廠
        FactoryA appleF = new AppleFactoryA();
        FactoryA bananaF = new BananaFactoryA();
        FactoryA peatF = new PearFactory();

        // 通過各個工廠去得到對應的水果
        FruitA apple = appleF.getFruit();
        FruitA banana = bananaF.getFruit();
        FruitA pear = peatF.getFruit();

        apple.get();
        banana.get();
        pear.get();
    }
}

如上代碼,如果以后有新的水果出現,比如橘子,那么不用修改工廠類,只需要增加一個橘子以及橘子的工廠類,且橘子工廠同時去遵循工廠的接口即可,客戶端調用就可以了,而其他已經寫好的工廠類無需修改!完全符合OCP原則,同時每個具體工廠子類只負責對應水果的生成,也遵守了單一職責原則

類圖如下

簡單工廠和工廠方法模式對比

工廠方法模式與簡單工廠模式在結構上的不同不是很明顯。

1、工廠方法類的核心是一個抽象工廠類,而簡單工廠模式把核心放在一個具體類上。

2、工廠方法模式之所以有一個別名叫多態性工廠模式,是因為具體工廠類都有共同的接口,或者有共同的抽象父類。

系統擴展需要添加新的產品對象時,僅僅需要添加一個具體對象以及一個具體工廠對象,原有工廠對象不需要任何修改,也不需要修改客戶端原有的代碼,很好的符合了“開放-封閉”原則。

而簡單工廠在添加新產品對象后,不得不修改工廠方法,擴展性不好。

工廠方法模式退化后可以演變成簡單工廠模式。

JDK 中的工廠方法模式

JAVA的 API 里使用了工廠方法模式的也很多很多,常用的比如,Object 類里的 toString() 方法,Java 的任何類,都可以繼承或者覆寫toString 方法,Object 相當於抽象工廠類,各個Java 的類,相當於具體工廠類。

還有 util 包里的Calendar類,該類是一個抽象類,它為特定瞬間與一組諸如 YEARMONTHDAY_OF_MONTHHOUR 等日歷字段之間的轉換提供了一些方法,並為操作日歷字段(例如獲得下星期的日期)提供了一些方法。

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
    public static Calendar getInstance() {
        return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
    }
}

該節選的代碼片段有一個靜態的 getInstance() 方法,return 了一個具體的日歷對象,Calendar 是工廠方法模式里的抽象工廠類,而具體工廠子類(簡單舉幾個例子)有JapaneseImperialCalendar,GregorianCalendar……

createCalendar 方法是 private 的,是屬於 getInstance 方法里的,也就是說 Calendar 這個抽象工廠類返回具體子類的對象,JDK 里這樣說:

與其他語言環境敏感類一樣,Calendar 提供了一個類方法 getInstance,以獲得此類型的一個通用的對象。Calendar 的 getInstance 方法返回一個 Calendar 對象,其日歷字段已由當前日期和時間初始化……

客戶端通過該方法返回對應的日歷工廠,客戶端再通過這些日歷工廠去生成對應的日歷產品……將來如果需要支持某個其他地區的特殊歷法,程序員除了必要的增加對應的日歷工廠並 extends Calendar 這個抽象工廠,且增加對應的日歷產品之外,只需要增加 Calendar 的 getInstance 方法的新的邏輯,但 Calendar 的使用者無需承擔這種變化的影響,符合OCP。

歡迎關注

dashuai的博客是終身學習踐行者,大廠程序員,且專注於工作經驗、學習筆記的分享和日常吐槽,包括但不限於互聯網行業,附帶分享一些PDF電子書,資料,幫忙內推,歡迎拍磚!

 


免責聲明!

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



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