一文搞懂三種工廠模式

上一篇文章詳細學習了單例模式的多種寫法,今天來學習一下以下三種模式:簡單工廠、工廠方法、抽象工廠模式,其實簡單工廠模式不屬於 GOF 23 種設計模式,不過它實現簡單,在有些場景下它也比較適用,因此就首先來看一下它。java

簡單工廠模式

一般咱們使用 new 關鍵字就能夠建立對象,爲何還要使用工廠模式呢?咱們如下面這個例子來看一下。git

若是有一個手機店,出售 IPhone、Samsung、Huawei 品牌的手機。github

public class Phone {
    public void pay() {}
    public void box() {}
}

class IPhone extends Phone {
}

class Samsung extends Phone {
}

class Huawei extends Phone {
}
複製代碼

顧客在購買手機的代碼能夠這樣寫:設計模式

public class PhoneStore {

    public Phone buyPhone(String type) {
        Phone phone = null;
        if ("Iphone".equals(type)) {
            phone = new IPhone();
        } else if ("Samsung".equals(type)) {
            phone = new Samsung();
        } else if ("Huawei ".equals(type)) {
            phone = new Huawei();
        }
        phone.pay();
        phone.box();
        return phone;
    }
}
複製代碼

若是店鋪想要增長競爭力,又添加了幾種手機品牌,就須要去修改 buyPhone 方法,在其中繼續添加 if-else 語句。框架

也就是說,若是代碼有變化或擴展,就必須從新修改該方法,這就違反了對擴展開放、對修改關閉的原則。並且這樣修改對於系統來講,將難以維護和更新。dom

其實,咱們能夠將建立對象的代碼移到另外一個對象,封裝成一個工廠類,在添加或改變手機的品牌時,只須要修改該工廠類便可:ide

public class SimplePhoneFactory {

    public static Phone createPhone(String type) {
        Phone phone = null;
        if ("Iphone".equals(type)) {
            phone = new IPhone();
        } else if ("Samsung".equals(type)) {
            phone = new Samsung();
        } else if ("Huawei ".equals(type)) {
            phone = new Huawei();
        }
        return phone;
    }
}
複製代碼

而 PhoneStore 的代碼就能夠修改成:學習

public class PhoneStore {

    public Phone buyPhone(String type) {
        Phone phone = SimplePhoneFactory.createPhone(type);
        phone.pay();
        phone.box();
        return phone;
    }
}
複製代碼

上述模式就是簡單工廠模式,也能夠利用靜態方法來定義工廠,這稱爲靜態工廠。ui

咱們來看一下它的 UML 圖:spa

下面來總結一下,簡單工廠模式有哪些優缺點。

優勢:

  • 若是一個調用者想建立一個對象,只要知道其名稱就能夠了。
  • 若是想增長一個產品,只要實現一個新的擴展自工廠類的子類就能夠了。
  • 它屏蔽了產品的具體實現,調用者只須要關心產品的接口。

但它也有一些缺點:

  • 每次增長產品時,都須要增長一個產品類,使得系統中的類太多,增長了系統的複雜度。

簡單工廠模式具體實踐

Calendar#createCalendar

該方法部分源碼以下:

private static Calendar createCalendar(TimeZone zone, Locale aLocale) ··· 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);
    }
}
複製代碼

這裏會根據傳入的語言和國家來決定生成什麼 Calendar。

工廠方法模式

若是手機店規模比較大,但願開設 IPhone、Samsung、Huawei 專賣分店(假設分店本身製做手機),這應該如何擴展呢?

因爲購買手機的過程相似,都須要付款、打包;而分店則須要生產其相應品牌的手機。咱們能夠將 PhoneStore 修改成抽象類 ,將 SimplePhoneFactory 的 createPhone 方法改成抽象方法,放置到 AbstractPhoneStore 中。

public abstract class PhoneStore {

    public Phone buyPhone() {
        Phone phone = createPhone();
        phone.pay();
        phone.box();
        return phone;
    }

    protected abstract Phone createPhone();
}
複製代碼

IPhone、Samsung、Huawei 三種產品分別以下:

public class Phone {
    public void pay() { }
    public void box() { }
}

class IPhone extends Phone { 
}

class Samsung extends Phone {
}

class Huawei extends Phone { 
}
複製代碼

三種產品對應的 IPhone、Samsung、Huawei 三家分店,它們的具體實現以下:

public class IPhoneStore extends PhoneStore {

    @Override
    protected Phone createPhone() {
        return new IPhone();
    }
}

public class SamsungStore extends PhoneStore {

    @Override
    protected Phone createPhone() {
        return new Samsung();
    }
}

public class HuaweiStore extends PhoneStore {

    @Override
    protected Phone createPhone() {
        return new Huawei();
    }
}
複製代碼

若是咱們要 IPhone 手機,代碼能夠以下:

public class Client {

    public static void main(String[] args) {
        PhoneStore phoneStore = new IPhoneStore();
        Phone phone = phoneStore.buyPhone();
        // phone 爲 IPhone
    }
}
複製代碼

上述這種模式就是工廠方法模式,它會定義一個建立對象的接口,但讓實現這個接口的類來決定實例化哪一個類。例如這裏建立了一個 PhoneStore 抽象類,但實際上由 IPhoneStore 來決定實例化哪一個 Phone 的實現類。

咱們能夠看到工廠方法模式包括了四個角色:

  • Product:抽象產品,對應上面的 Phone;
  • ConcreteProduct:具體產品,對應 IPhone、Samsung、Huawei 等;
  • Factory:抽象工廠,對應 PhoneStore;
  • ConcreteFactory:具體工廠,對應 IPhoneStore、SamsungStore、HuaweiStore。

它的 UML 圖以下:

下面總結一下工廠方法模式的優勢:

  • 用戶只須要關心所需產品對應的工廠,而無需關心建立產品的細節;
  • 在系統中加入新產品時,無需修改抽象工廠和抽象產品的接口,也無需修改其餘的具體工廠和具體產品,只須要添加一個具體的工廠和具體產品就能夠了。

缺點:

  • 每添加一個新產品就要添加對應的工廠和產品,形成系統中類的個數太多,增長了系統的複雜度。
  • 考慮系統的擴展性,須要引入抽象層,增長了系統的抽象性,系統實現的難度也加大。

簡單工廠模式與工廠方法模式之間的區別以下:

  • 在簡單工廠中,是將對象的建立封裝在另外一個類中;
  • 而在工廠方法中,它建立了一個框架,由子類來決定建立哪一個對象。

工廠方法模式具體實踐

Java 集合的 iterator 方法就是一個工廠方法。部分集合的 UML 圖以下:

抽象工廠

該實例中抽象工廠就是 Iterable 接口:

public interface Iterable<T> {
    Iterator<T> iterator();
}
複製代碼

具體工廠

具體工廠在 Java 集合中很是多,這裏舉兩個例子,例如在 ArrayList 中的實現:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable public Iterator<E> iterator() {
        return new Itr();
    }
}
複製代碼

在 HashMap 中的實現中,entrySet 方法返回一個 EntrySet 對象:

final class EntrySet extends AbstractSet<Map.Entry<K,V>> {

    public final Iterator<Map.Entry<K,V>> iterator() {
        return new EntryIterator();
    }
}
複製代碼

抽象產品

抽象產品就是 Iterator 接口

public interface Iterator<E> { 
    boolean hasNext();
    E next();
}
複製代碼

具體產品

這裏的具體產品,以上面說的 ArrayList 中的 Itr 和 EntrySet 中的 EntryIterator 爲例。

Itr 對象以下:

private class Itr implements Iterator<E> {
    public boolean hasNext() { ··· }
    public E next() { ··· }
}
複製代碼

EntryIterator 對象以下:

abstract class HashIterator {
    public final boolean hasNext() { ··· }
    final Node<K,V> nextNode() { ··· }
}

final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>> {
    public final Map.Entry<K,V> next() { return nextNode(); }
}
複製代碼

抽象工廠模式

若是想要改變商業模式,三家專賣店內不只能夠賣 IPhone、Sansung、Huawei 三種品牌的手機,也能夠賣相應品牌的電腦(假設分店本身生產手機、電腦),這應該如何設計呢?(這個例子不太符合實際狀況,不過能說明抽象工廠模式的含義,湊合看吧)

店鋪能夠以下設計:

public abstract class PhoneStore {

    public Phone buyPhone() {
        Phone phone = createPhone();
        phone.pay();
        phone.box();
        return phone;
    }

    public Computer buyComputer() {
        Computer computer = createComputer();
        computer.pay();
        computer.pack();
        return computer;
    }

    protected abstract Phone createPhone();
    protected abstract Computer createComputer();
}
複製代碼

三種品牌的手機類以下:

public class Phone {
    public void pay() { }
    public void box() { }
}

class IPhone extends Phone { 
}
class SamsungPhone extends Phone {
}
class HuaweiPhone extends Phone { 
}
複製代碼

三種品牌的電腦類以下:

public class Computer {
    public void pay() {  }
    public void pack() { }
}

class MacComputer extends Computer { 
}
class SamsungComputer extends Computer { 
}
class HuaweiComputer extends Computer { 
}
複製代碼

對於三家相應品牌的專賣店,它們的具體實現以下:

public class IPhoneStore extends PhoneStore {

    @Override
    protected Phone createPhone() {
        return new IPhone();
    }

    @Override
    protected Computer createComputer() {
        return new MacComputer();
    }
}

public class SamsungStore extends PhoneStore {

    @Override
    protected Phone createPhone() {
        return new SamsungPhone();
    }

    @Override
    protected Computer createComputer() {
        return new SamsungComputer();
    }
}

public class HuaweiStore extends PhoneStore {

    @Override
    protected Phone createPhone() {
        return new HuaweiPhone();
    }

    @Override
    protected Computer createComputer() {
        return new HuaweiComputer();
    }
}
複製代碼

若是咱們要在 IPhone 專賣店購買手機和電腦,代碼能夠以下:

public class Test {

    public static void main(String[] args) {
        PhoneStore phoneStore = new IPhoneStore();
        Phone phone = phoneStore.buyPhone();
        Computer computer = phoneStore.buyComputer();
        // phone 爲 IPhone
        // computer 爲 Mac
    }
}
複製代碼

上述的模式就是抽象工工廠模式,它提供了一個接口,用於建立一個產品的家族,而不須要指定具體類。每一個具體工廠會建立某個產品家族。

在上述例子,IPhoneStore、SamsungStore、HuaweiStore 就是一個個具體的工廠,它們能夠生產對應品牌的手機和電腦。其中 IPhoneStore 這個工廠就是建立 IPhone、MacComputer 這個產品家族。

它的 UML 圖以下:

下面總結一下抽象工廠模式的優缺點。

優勢:

  • 一樣地,將用戶代碼和實際的具體產品解耦,使其無需關心產品的建立細節;
  • 使用某個工廠,能夠建立一系列相關的產品。若是想要增長一條個產品線,例如上面想要增長一個新的品牌店和其相應的產品,只須要擴展 PhoneStore 工廠,並建立相應的 Phone、Computer 類便可,很是簡單。

缺點:

  • 限制了所能建立的產品集合,例如上面的 Phone 和 Computer,若是想要增長新的產品,增長 camera,就會比較困難,須要修改抽象工廠的接口,會增長很大的工做量;
  • 另外,工廠類和產品類較多,增長了系統的抽象性和複雜度。

抽象工廠模式與工廠方法模式很相似,它們之間的區別以下:

  • 在工廠方法模式中,每一個具體工廠負責建立一個具體產品。因此,在增長一個具體產品時,也要增長其相應的工廠,須要建立一個繼承自抽象工廠的類,並覆蓋它的工廠方法。也就是所說的工廠方法使用繼承建立對象。
  • 而在抽象工廠模式中,每一個具體工廠負責建立一個系列的具體產品。因此,只有在新增長一個類型的具體產品時,才須要增長相應的工廠。它能夠用來建立一系列具體產品,將這些相關的產品組合起來,也就是所說的使用組合建立對象。

它們之間也有一些關聯,就是抽象工廠的方法以工廠方法的方法來實現。在抽象工廠的接口中,每一個方法都負責建立一個具體產品,而具體工廠來提供具體的實現。

例如,PhoneStore 中的 createPhone、createComputer 方法由子類實現,這兩個方法單獨來看都是在建立一個對象,其實也就是一個工廠方法。

抽象工廠模式的實踐

JDBC 中的 Connection 就是一個抽象工廠模式,在不一樣的鏈接池中有不一樣的實現,例如 druid 和 dbcp:

因爲本人對於 druid 和 dbcp 的實現也不太熟悉,這裏就很少解釋了,有興趣的小夥伴能夠本身研究一下。

參考資料

相關文章
相關標籤/搜索