設計模式:用實際案例講解工廠模式

工廠模式有啥用啊,我的項目沒使用工廠模式也照樣運行

這是我聽過最令人哭笑不得的吐槽,這個程式猿的頭髮不知道有沒有被自己薅禿

的確,項目中不使用工廠模式並不會影響項目的運行

但是,當項目後期需要二次開發時,程式碼的維護和修改的複雜度,絕對能讓你恨不得把自己頭髮都薅禿

下面我們就來盤一盤工廠模式能解決哪些問題

簡單工廠模式

實際案例

假如客戶有這樣一個需求,做一個用戶訂購手機來玩遊戲的項目

項目中可以生產華為和小米的手機,生產的手機只能用來玩遊戲,用戶可以通過京東和淘寶來訂購手機

需求中的一個前置條件是手機只能用來玩遊戲,不能做別的事情。這就類似於一個規範,所有的手機都要遵守這個規範。

制定規範是java中介面的特性,所以我們要定義一個介面基類,叫做Phone,裡面有一個玩遊戲的方法play()

還要有華為手機和小米手機的類,分別叫做HuweiXiaomi,這兩個手機類要遵循手機只能用來玩遊戲這個規範,所以它們要實現Phone類,並完成play()方法

用java程式碼實現分別如下

phone基類

public interface Phone {
    void play();
}

華為手機類Huawei

public class Huawei implements Phone {
    @Override
    public void play() {
        System.out.println("華為手機玩遊戲");
    }
}

小米手機類Xiaomi

public class Xiaomi implements Phone {
    @Override
    public void play() {
        System.out.println("小米手機玩遊戲");
    }
}

用戶可以通過京東和淘寶來訂購手機,所以還要有京東和淘寶的類,分別叫做JingdongTaobao。類裡面各有一個訂購方法叫做order(),可以根據用戶的喜好來訂購不同的手機

如果用戶喜歡華為則訂購華為手機來玩遊戲,如果用戶喜歡小米則訂購小米手機來玩遊戲,其他的用戶就不能玩遊戲

因為京東類和淘寶類的程式碼邏輯一摸一樣,這裡只貼一下京東類的程式碼

public class Jingdong {
    public void order(String type) {
        Phone phone = null;
        if ("huawei".equals(type)) {
            phone = new Huawei();
            phone.play();
        } else if ("xiaomi".equals(type)) {
            phone = new Xiaomi();
            phone.play();
        } else {
            System.out.println("暫不支援");
        }
    }
}

這樣我們就實現了客戶的需求,而且沒有使用任何設計模式。

項目雖然可以完美的運行,但是有一個問題值得我們思考,假如又增加了蘋果手機,這時候我們的程式碼該怎麼修改

首先,我們肯定是要增加蘋果手機類IPhone,並且實現Phone基類

京東類中訂購方法的邏輯需要做出相應的修改

淘寶類中訂購方法的邏輯也需要做出相應的修改,修改的地方和京東類一模一樣。這時我們就發現同樣的程式碼需要修改兩次。

如果訂購類有很多,除了京東、淘寶,還有拼多多、微信商城等等。那就意味著同樣的程式碼不止要修改兩次,有多少個訂購類就需要修改多少次

這個工作量是很大的,而且極其容易出錯,就問你頭禿不頭禿

這時候就需要使用簡單工廠模式來優化我們的程式碼

簡單工廠模式就是創建一個工廠類,由這個類來封裝實例化對象的行為

套用到這個例子中就是:創建一個工廠類,由工廠類來封裝創建手機的邏輯。訂購類從工廠類中獲取手機對象直接使用,不再關心手機創建的過程

工廠類PhoneFactory用程式碼實現就是這樣

public class PhoneFactory {
    public static Phone createPhone(String type) {
        Phone phone = null;
        if ("huawei".equals(type)) {
            phone = new Huawei();
        } else if ("xiaomi".equals(type)) {
            phone = new Xiaomi();
        } else if ("apple".equals(type)) {
            phone = new IPhone();
        } else {
            System.out.println("暫不支援");
        }
        return phone;
    }
}

京東、淘寶等訂購類從工廠類裡面獲取手機對象後直接使用,不再關心手機的創建過程(京東和淘寶類的程式碼一樣,這裡只貼京東類的程式碼)

public class Jingdong {
    public void order(String type) {
        Phone phone = PhoneFactory.createPhone(type);
        if (phone != null) {
            phone.play();
        }
    }
}

這就實現了簡單工廠模式,以後再需要增加手機型號時只需要修改工廠類就行了,其他程式碼不用修改

簡單工廠模式總結

簡單工廠模式就是創建一個工廠類,根據傳入的參數類型來創建具體的產品對象,並返回產品對象的實例

主要適用於調用者不知道應該創建哪個具體的對象,只能根據傳入的條件返回相應對象的場景

比如案例中,訂購類是不知道要創建哪個手機對象的,只能根據用戶提供的條件才能知道需要哪個手機對象

簡單工廠模式的好處在於將對象的創建過程和使用過程進行解耦,減少新增具體產品時修改程式碼的複雜度

就像上面的例子一樣,對象的創建過程由工廠類負責,訂購類不需要關心對象是怎麼創建的,只需要從工廠類獲取對象來使用即可

當需要增加手機對象時,只需要修改工廠類,而不需要對每一個訂購類進行修改

簡單工廠模式的缺點在於每次新增具體產品時,都需要修改工廠類,這違背了設計模式中的開閉原則。而且當具體的產品比較多時,工廠類的if-else判斷就會比較多,程式碼不美觀

工廠方法模式

實際案例

基於剛才用戶訂購手機玩遊戲的需求,我們稍微改動一下。

為了實現精準營銷,京東、淘寶等商城分別上線了華為專賣店、小米專賣店和蘋果專賣店

當用戶進入華為專賣店,就默認用戶要訂購華為手機;當用戶進入小米專賣店,就默認用戶要訂購小米手機;當用戶進入蘋果專賣店,就默認用戶要訂購蘋果手機

這個需求用剛才我們講的簡單工廠模式也可以實現

但是簡單工廠的缺點也很明顯,當新增粉絲類型時需要修改工廠類,當粉絲類型過多時工廠類的邏輯就會比較繁雜

比如新增了vivo粉絲,工廠類就需要新增判斷條件去創建vivo手機對象;新增了oppo手機,工廠類就要新增判斷條件去創建oppo手機對象。一直不斷的新增下去的話,就會導致工廠類的中的判斷過多,程式碼很長,後期不容易維護

而且,簡單工廠模式是適用於調用者不知道應該創建哪種對象的場景。

在這個需求中,京東等訂購類中為不同的粉絲提供了專賣店,假如專賣店是訂購類中的一個方法的話,在專賣店這個方法中是知道應該創建什麼樣的對象的。比如,在華為手機的訂購方法中,是知道要創建華為手機對象的

所以,這個需求可以用工廠方法模式來實現

工廠方法模式和簡單工廠模式相似,也需要有一個工廠類。不過在工廠方法模式中,工廠類不再負責創建對象。因為在每個訂購方法中已經知道應該創建哪個手機對象,所以創建對象的邏輯下沉到訂購類的方法中

工廠類只負責制定規範,來約束每個產品的具體行為,所以工廠類是一個介面基類。每個對應的產品需要有一個自己的工廠,來繼承這個基類,達到約束自身行為的目的

這個需求中工廠基類規定每個工廠只能有一個方法,這個方法的作用就是創建手機對象

工廠基類PhoneFactory用程式碼實現

public interface PhoneFactory {
   Phone createPhone();
}

華為工廠類用程式碼實現

public class HuaweiFactory implements PhoneFactory {
   @Override
   public Phone createPhone() {
      return new Huawei();
   }
}

小米工廠類用程式碼實現

public class XiaomiFactory implements PhoneFactory {
   @Override
   public Phone createPhone() {
      return new Xiaomi();
   }
}

在訂購類中,不同的訂購方法調用不同的工廠獲取對象。比如京東訂購類的程式碼如下(淘寶訂購類處理邏輯類似,這裡不再貼淘寶類的程式碼)

public class Jingdong {
    // 華為粉絲訂購華為手機
    public void orderHuawei() {
        PhoneFactory phoneFactory = new HuaweiFactory();
        Phone phone = phoneFactory.createPhone();
        phone.play();
    }
    // 小米粉絲訂購小米手機
    public void orderXiaomi() {
        PhoneFactory phoneFactory = new XiaomiFactory();
        Phone phone = phoneFactory.createPhone();
        phone.play();
    }
}

這樣我們就用工廠方法模式實現了為不同粉絲訂購不同手機的需求

工廠方法模式總結

工廠方法模式是定義一個工廠介面基類,基類中定義一個創建產品的抽象方法。每個產品需要有自己的工廠來實現這個基類,並完成創建對應產品實例的方法,由具體的產品調用該方法來創建對象

它主要適用於調用者已經明確知道需要創建哪一個具體產品的場景

比如,針對華為的粉絲,已經明確知道要創建華為的手機產品

工廠方法模式的優勢在於完全符合了開閉原則,在新增產品時不需要再改動已存在的程式碼,使工廠類和產品類的程式碼完全解耦,更利於程式的擴展

他的缺點也很明顯,當新增產品時,需要同時新增產品類和工廠類,導致系統中的類是成對增加,增加了系統的複雜度

抽象工廠模式

實際案例

基於工廠方法模式的案例,我們再進一步擴展

用戶不單單想訂購手機來玩遊戲,還想訂購ipad和電腦

可以用剛才講的工廠方法模式來實現:我們不僅需要提供手機工廠的基類,還需要提供ipad工廠基類和電腦工廠基類,並且為每個工廠基類提供具體的工廠實現類

訂購類方法中,根據不同的需求來創建不同的產品供用戶使用

這樣實現的程式碼沒有問題,但是不符合我們真實開發中的業務場景

在實際業務場景中,京東商城的華為專賣店想要訂購手機不需要到華為公司的手機部門去訂購吧?想要訂購ipad不需要到華為公司的ipad部門訂購吧?想要訂購電腦也不需要到華為公司的電腦部門訂購吧?

京東商城的華為專賣店應該只負責和華為公司對接,和具體的業務部門沒關係。專賣店想要訂購某個產品去告訴華為公司,由公司去給具體的業務部門溝通

所以,從實際的使用場景出發,我們的程式碼應該這樣設計

不再使用單獨的手機工廠、ipad工廠和PC工廠,而是把同一個廠家作為工廠,由工廠分別創建不同的產品

Factory基類實現程式碼如下

public interface Factory {
    Phone createPhone();
    IPad createIPad();
    PC createPC();
}

華為工廠類實現程式碼如下

public class HuaweiFactory implements Factory {
    @Override
    public Phone createPhone() {
        return new HuaweiPhone();
    }
    @Override
    public IPad createIPad() {
        return new HuaweiIPad();
    }
    @Override
    public PC createPC() {
        return new HuaweiPC();
    }
}

小米工廠實現程式碼如下

public class XiaomiFactory implements Factory {
    @Override
    public Phone createPhone() {
        return new XiaomiPhone();
    }
    @Override
    public IPad createIPad() {
        return new XiaomiIPad();
    }
    @Override
    public PC createPC() {
        return new XiaomiPC();
    }
}

在京東訂購類中,我們只需要創建對應的工廠對象,由工廠對象創建不同的產品

public class Jingdong {
    // 華為粉絲訂購手機、ipad、電腦
    public void orderHuawei() {
        Factory factory = new HuaweiFactory();
        Phone phone = factory.createPhone();
        phone.play();
        IPad ipad = factory.createIPad();
        ipad.play();
        PC pc = factory.createPC();
        pc.play();
    }
    // 小米粉絲訂購手機、ipad、電腦
    public void orderXiaomi() {
        Factory factory = new XiaomiFactory();
        Phone phone = factory.createPhone();
        phone.play();
        IPad ipad = factory.createIPad();
        ipad.play();
        PC pc = factory.createPC();
        pc.play();
    }
}

這樣我們就用抽象工廠模式實現了用戶訂購手機、ipad和電腦的需求

抽象工廠模式總結

抽象工廠模式是將具有一定共性的產品封裝到一塊,由工廠類分別為這些產品提供創建對象的方法,調用者可以根據不同的需求調用工廠類的具體方法來獲得產品實例

比如案例中華為的手機、ipad和電腦都屬於華為公司產品,所以可以由華為工廠類來負責分別創建不同的對象

它的優勢在於將具有一定共性的產品集合封裝到一起,在實際開發中更符合具體的業務場景

他的缺點就是降低了系統的擴展性,當新增產品時需要修改工廠類,在工廠類的基類和實現類中都需要增加對應的方法

比如說,用戶也想訂購VR眼鏡來玩遊戲。那麼工廠基類中需要增加創建VR眼鏡的方法,所有的工廠實現類中都需要增加對該方法的實現,系統擴展性比較差

— 文章來自赫連小伍公眾號