初識設計模式

2022-08-30 12:02:25

簡介

工廠設計模式(Factory Design Pattern)是一種建立型的設計模式,它提供了一種建立物件的最佳方式,是一種代替 new 操作符的一種模式。

在工廠模式中,建立物件不會對使用者端暴露建立邏輯,而是通過使用一個共同的介面來指向新建立的物件。

工廠模式還可以細分為三種的型別:簡單工廠模式、工廠方法模式和抽象工廠模式。

簡單工廠模式

概念

簡單工廠模式(Simple Factory Pattern)的定義是,由一個工廠物件決定建立出哪一種產品類的範例,被建立的產品類範例具有共同的父類別或實現同樣的介面。

因為在簡單工廠模式中用於建立範例的方法通常是靜態方法,因此簡單工廠模式又被稱為靜態工廠模式(Static Factory Pattern)。

實現方式

下面是使用果汁生產來展示簡單工廠模式:

果汁 FruitJuice 介面:描述生產果汁的必要方法

public interface FruitJuice {
    void make();
}

蘋果汁 AppleJuice 類:生產蘋果汁的類

public class AppleJuice implements FruitJuice {
    public AppleJuice() {
        this.make();
    }

    @Override
    public void make() {
        System.out.println("This is AppleJuice make!");
    }
}

橙汁 OrangeJuice 類:生產橙汁的類

public class OrangeJuice implements FruitJuice {
    public OrangeJuice() {
        this.make();
    }

    @Override
    public void make() {
        System.out.println("This is OrangeJuice make!");
    }
}

果汁工廠 FruitJuiceFactory 類:負責生產各種果汁的類

public class FruitJuiceFactory {
    public FruitJuiceFactory() {}

    public static FruitJuice createFruitJuice(String juiceType) {
        FruitJuice fruitJuice = null;
        if (juiceType.equals("AppleJuice")) {
            fruitJuice = new AppleJuice();
        } else if (juiceType.equals("OrangeJuice")) {
            fruitJuice = new OrangeJuice();
        }
        return fruitJuice;
    }
}

通過 if-else 邏輯是簡單工廠的第一種實現方法,還可以通過靜態程式碼塊和 Map 結構實現簡單工廠模式,具體的程式碼範例如下:

import java.util.Map;
import java.util.HashMap;

public class FruitJuiceFactory {
    // 儲存產品類的對映關係
    private static final Map<String, FruitJuice> cachedFruitJuice = new HashMap<>();

    static {
        cachedFruitJuice.put("AppleJuice", new AppleJuice());
        cachedFruitJuice.put("OrangeJuice", new OrangeJuice());
    }

    public FruitJuiceFactory() {}

    public static FruitJuice createFruitJuice(String juiceType) {
        if (juiceType == null || juiceType.isEmpty()) {
            return null;
        }
        // 直接從 Map 結構中取到對應的產品類範例
        return cachedFruitJuice.get(juiceType.toLowerCase());
    }
}

優點

簡單工廠模式的主要優點如下:

  • 簡單工廠模式實現了物件建立和使用的分離
  • 使用者端無需知道所建立的具體產品類的類名,只需要知道具體產品類所對應的引數即可
  • 可以在不改變使用者端程式碼的情況下更換或增加新的具體產品類

缺點

簡單工廠模式的主要缺點如下:

  • 工廠類集中了相似產品類的建立邏輯,職責過重
  • 簡單工廠模式違反開閉原則,新增產品類時需要改動到工廠類
  • 通常使用靜態方法作為建立範例的方法,無法實現繼承關係

適用場景

簡單工廠模式的適用場景如下:

  • 對於一批產品類,並且不會新增產品類,可以選擇簡單工廠模式

原始碼

在 JDK 中,java.util.Calendar 使用了簡單工廠模式。如下是其的一些實現邏輯:

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
    private static Calendar createCalendar(TimeZone zone, Locale aLocale) {
        CalendarProvider provider =
            LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
                                 .getCalendarProvider();
        if (provider != null) {
            try {
                return provider.getInstance(zone, aLocale);
            } catch (IllegalArgumentException iae) {
                // fall back to the default instantiation
            }
        }

        Calendar cal = null;

        if (aLocale.hasExtensions()) {
            String caltype = aLocale.getUnicodeLocaleType("ca");
            if (caltype != null) {
                cal = switch (caltype) {
                    case "buddhist" -> new BuddhistCalendar(zone, aLocale);
                    case "japanese" -> new JapaneseImperialCalendar(zone, aLocale);
                    case "gregory"  -> new GregorianCalendar(zone, aLocale);
                    default         -> null;
                };
            }
        }
        if (cal == null) {
            if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
                cal = new BuddhistCalendar(zone, aLocale);
            } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
                       && aLocale.getCountry() == "JP") {
                cal = new JapaneseImperialCalendar(zone, aLocale);
            } else {
                cal = new GregorianCalendar(zone, aLocale);
            }
        }
        return cal;
    }
}

工廠方法模式

概念

簡單工廠模式違背了開閉原則,沒有能夠做到「對擴充套件開放、對修改關閉」,新增產品類時需要改動到工廠類。

而工廠方法模式(Factory Method Pattern)是對簡單工廠模式的進一步抽象,其好處是可以使系統在不修改原來程式碼的情況下引入新的產品類,即滿足開閉原則。

和簡單工廠模式中工廠負責生產所有的產品相比,工廠方法模式抽象出簡單工廠的介面,然後將生產具體產品的任務分發給具體的產品工廠,即簡單工廠的工廠。

由於工廠方法模式利用了物件導向的多型特性,因此又被稱為多型工廠模式(Polymorphic Factory Pattern)。

實現方式

在上述簡單工廠模式的基礎上,將果汁生產改造成工廠方法模式:

抽象工廠 AbstractFactory 介面:描述生產果汁工廠的必要方法

public interface FruitJuiceFactory {
    FruitJuice createFruitJuice();
}

蘋果汁工廠 AppleJuiceFactory 類:詳細的生產蘋果汁的類

public class AppleJuiceFactory implements FruitJuiceFactory {
    public AppleJuiceFactory() {}

    @Override
    public FruitJuice createFruitJuice() {
        return new AppleJuice();
    }
}

橙汁工廠 OrangeJuiceFactory 類:詳細的生產橙汁的類

public class OrangeJuiceFactory implements FruitJuiceFactory {
    public OrangeJuiceFactory() {}

    @Override
    public FruitJuice createFruitJuice() {
        return new OrangeJuice();
    }
}

優點

工廠方法模式除了具有簡單工廠模式的優點之外,還有以下優點:

  • 在系統中增加新的產品類時,無需修改原有的工廠程式碼,只需新增一個具體產品類和具體工廠類

缺點

工廠方法模式的主要缺點如下:

  • 新增新的產品類時,需要同時提供具體產品類和對應的工廠類,系統中類的個數將成對增加
  • 工廠方法模式引入了抽象層,增加了系統的抽象性和理解難度

適用場景

工廠方法模式的適用場景如下:

  • 使用者端知道其需要的介面實現類,不知道所需要的具體物件類,可以通過對應的工廠建立範例

原始碼

在 JDK 中,java.util.Collection 介面中定義的 iterator() 方法就是一個工廠方法。

所有實現了 Collection 介面的類,都需要顯式地實現此方法並返回一個 Iterator 範例,不同的實現類可以擁有自己的實現邏輯。

抽象工廠模式

概念

在工廠方法模式中,具體工廠負責生產具體的產品,每個具體工廠對應一種具體產品,工廠方法具有唯一性。

抽象工廠模式(Abstract Factory Pattern)定義了一個介面用於建立相關或有依賴關係的物件族,而無需指明具體的類,其可以整合簡單工廠模式和抽象工廠模式。

學習抽象工廠模式需要理解兩個概念:產品等級結構指的是產品的繼承結構,如抽象果汁和具體果汁之間構成一個產品等級結構;產品族指的是由同一個工廠生產的,位於不同等級結構中的一組產品,如同一個飲料工廠生產的酒和果汁構成一個產品族。

從上述概念上理解,工廠方法模式針對的是一個產品等級結構,而抽象工廠模式在工廠方法模式的基礎上,覆蓋了多個工廠的產品族。

實現方式

在上述簡單工廠模式的基礎上,下面使用果汁生產和酒生產來展示抽象工廠模式:

酒 Alcohol 介面:描述生產酒的必要方法

public interface Alcohol {
    void make();
}

葡萄酒 Wine 類:詳細的生產葡萄酒的類

public class Wine implements Alcohol {
    public Wine() {
        this.make();
    }

    @Override
    public void make() {
        System.out.println("This is Wine make!");
    }
}

啤酒 Beer 類:詳細的生產啤酒的類

public class Beer implements Alcohol {
    public Beer() {
        this.make();
    }

    @Override
    public void make() {
        System.out.println("This is Beer make!");
    }
}

抽象工廠 AbstractFactory 介面:描述生產果汁和酒的必要方法

public interface AbstractFactory {
    FruitJuice creatFruitJuice();
    Alcohol createAlcohol();
}

具體工廠 ConcreteFactory 類:可生產果汁和酒的工廠的類

public class ConcreteFactory implements AbstractFactory {
    public ConcreteFactory() {}

    @Override
    public FruitJuice creatFruitJuice(String juiceType) {
        FruitJuice fruitJuice = null;
        if (juiceType.equals("AppleJuice")) {
            fruitJuice = new AppleJuice();
        } else if (juiceType.equals("OrangeJuice")) {
            fruitJuice = new OrangeJuice();
        }
        return fruitJuice;
    }

    @Override
    public Alcohol createAlcohol(String alcoholType) {
        Alcohol alcohol = null;
        if (alcoholType.equals("Wine")) {
            alcohol = new Wine();
        } else if (alcoholType.equals("Beer")) {
            alcohol = new Beer();
        }
        return alcohol;
    }
}

優點

抽象工廠模式的主要優點如下:

  • 當一個產品族中的多個物件被設計成一起工作時,它能夠保證使用者端始終只使用同一個產品族中的物件
  • 增加新的產品族很方便,無需修改已有程式碼,符合開閉原則

缺點

抽象工廠模式的主要缺點如下:

  • 增加新的產品等級結構麻煩,需要對原有系統進行較大的修改,甚至需要修改抽象層程式碼

適用場景

抽象工廠模式的適用場景如下:

  • 當需要建立的物件是一系列相互關聯或相互依賴的產品族時,便可以使用抽象工廠模式
  • 產品等級結構穩定,設計完成之後,不會向系統中增加新的產品等級結構或者刪除已有的產品等級結構

原始碼

在 JDK 中,java.util.Collection 介面可以看作一個抽象工廠。

Collection 介面定義了轉換成 Iterator 範例的工廠方法,也定義了轉換成 T[] 範例的工廠方法,可以認定其做了兩個產品族的定義。

總結

使用標準

對於要不要使用工廠模式,其最本質的參考標準有以下四個:

  • 封裝變化:建立邏輯有可能變化,封裝成工廠類之後,建立邏輯的變更對呼叫者透明
  • 程式碼複用:建立程式碼抽離到獨立的工廠類之後可以複用
  • 隔離複雜性:封裝複雜的建立邏輯,呼叫者無需瞭解如何建立物件
  • 控制複雜度:將建立程式碼抽離出來,讓原本的函數或類職責更單一,程式碼更簡潔

應用場景

對於產品種類相對較少、且可預見性地不會修改的情況,可以使用簡單工廠模式。使用簡單工廠模式的使用者端只需傳入工廠類的引數,不需要關心如何建立物件的邏輯,可以很方便地建立所需產品。

由於簡單工廠模式會將所有建立邏輯都放在一個工廠類中,會導致這個工廠類會變得很複雜,當產品的種類是可預見地會增加時,還需要對工廠類做更改,這種時候可以採用工廠方法模式以達到不需要修改原來程式碼的情況下引進新的產品。

抽象工廠模式最早的應用是用於建立屬於不同作業系統的視窗構件。當需要建立的物件是一系列相互關聯或相互依賴的產品族時,或者是隻會新增新的產品族而不是新種類的產品時,可以使用抽象工廠模式。