【23種設計模式專題】二 工廠模式

程序猿學社的GitHub,歡迎Star
github技術專題
本文已記錄到github

前言

通過上一篇文章,我們已經知道程序猿是有女(男)朋友,也知道如何保證只會有一個對象(男女朋友),本文我們來看一看工廠模式。

社長:“老王,我們開始繼續23中設計模式之工廠模式”
隔壁老王: “社長,工廠模式有什麼用,爲什麼要用工廠模式?”
社長: “我先買一個小關子,先來看一看傳統的寫法”

小故事

週末一大早,6點鐘左右,我就被吵醒,然後一臉呆萌呆萌的看着我,原來是忘記給我家乖乖餵食物咯,看了一下存放貓糧的袋子,也彈盡糧絕咯,只能上寵物店去購買貓糧。他的名字叫湯圓

傳統方式

通過代碼,我們實現去寵物店購買貓糧的這樣一個需求。

package com.cxyxs.designmode.factory;
/**
 * Description:
 * Author: 程序猿學社
 * Date:  2020/4/3 23:16
 * Modified By:
 */
public interface PetShop {
    void buy();
}

class  Meat implements  PetShop{

    @Override
    public void buy() {
        System.out.println("社長給湯圓購買肉乾");
    }
}

/**
 * 提供者
 * =======================
 * 調用者
 */
class  Test{
    public static void main(String[] args) {
        PetShop ps = new Meat();
        ps.buy();
    }
}


  • 這種寫法違反了迪米特法則也就是最少知道原則,通俗的來講,就是一個類對自己依賴的類知道的越少越好。也就是說,對於被依賴的類來說,無論邏輯多麼複雜,都儘量地的將邏輯封裝在類的內部,對外除了提供的public方法,不對外泄漏任何信息。
  • 結合生活實際理解,例如我這裏是去購買肉乾,我有必要知道這個肉是如何生產出來的嗎
  • 實際上也違反勒開閉原則,對擴展開放(提供者),對修改關閉(調用者)
    例如我們把類Meat改爲Meat1,我們可以發現,我們需要修改兩個地方,一個是提供者,還有一個是調用者,兩邊都要改動,這就違反了對修改關閉這一條,也沒有實現程序的一個解耦。

隔壁老王: “社長,你也說了,上面這種方式,提供者一變,調用者就得跟着變,在項目開發過程中,如何也出現這樣的問題,那大家還能不能愉快的玩耍咯”
社長:“別急,我們可以通過簡單工廠來實現”

簡單工廠(第一種)

從設計模式的類型上來說,簡單工廠模式是屬於創建型模式,又叫做靜態工廠方法(Static Factory Method)模式,但不屬於23種GOF設計模式之一。簡單工廠模式是由一個工廠對象決定創建出哪一種產品類的實例

肉乾畢竟不能當飯喫,所以,還得買貓糧,肉乾只能當零食喫。所以這時候我們的需求又發生了改變。

  • 不清楚這個圖如何畫的,可以百度查一下UML類圖
package com.cxyxs.designmode.factory.simple;

/**
 * Description:寵物店
 * Author: 程序猿學社
 * Date:  2020/4/3 23:16
 * Modified By:
 */
public interface PetShop {
    void buy();
}

class  Meat implements  PetShop{

    @Override
    public void buy() {
        System.out.println("社長給湯圓購買肉乾");
    }
}

class  Foot implements  PetShop{

    @Override
    public void buy() {
        System.out.println("社長給湯圓購買貓糧");
    }
}


class  CatFootFacoty{
    public  static  PetShop buy(String name){
        switch (name){
            case "貓糧":
                return  new Foot();
            case "肉乾":
                return  new Meat();
        }
        return  null;
    }
}

/**
 * 提供者代碼
 * =======================
 * 調用端代碼
 */
class  Test{
    public static void main(String[] args) {
        PetShop ps = CatFootFacoty.buy("肉乾");
        PetShop ps1 = CatFootFacoty.buy("貓糧");
        ps.buy();
        ps1.buy();
    }
}


好處:

  • 在這裏藉助了一個工廠類CatFootFacoty,實現解耦,我們不用關心肉乾和貓糧是生產生產的,只需要告訴工廠,我需要這兩樣東西。由工廠統一管理。
  • 產品很少的情況下,可以實現簡單工廠模式

缺點:

  • 如果產品一多,工廠類就會變得很臃腫,不利於維護,同時他違反咯開閉原則,開閉原則很重要的一點,當軟件需要變化時,儘量通過擴展軟件實體的行爲來實現變化,而不是通過修改已有代碼來實現變化。
  • 例如我現在需要新增一個購買魚的產品需求,需要新增一個魚類,再修改工廠類,類會變得越來越多,如果上百個產品,這就意味着上百個類,增加了程序的複雜度。

工廠方法模式(第二種)

跟簡單工廠相比,工廠方法模式是簡單工廠的plus版本,越來越流程化。不像簡單工廠一個工廠,既生產肉乾,又生產貓糧等等,工廠方法模式,就是把具體生產的工作,交給具體的工廠來負責,例如,生產肉乾的是一個工廠,生產貓糧的又是一個工廠。

  • 所以工廠方法模式符合開閉原則

社長: “我們剛剛已經瞭解到簡單工廠一些優缺點,其中很重要的一點就是不符合開閉原則,我們來看一看工廠方法模式”
社長: “既然工廠方法模式是簡單工廠的plus版本,我們直接把簡單工廠的代碼拿過來,新創建一個包methodmodel,老王,有沒有跟上我的節奏”
隔壁老王: “歐了,已經搞定咯,如何改造成工廠方法模式*
社長: "話不多說,先看看類圖,再看代碼 "

  • 這個類圖代碼寫完後,可以自動生成,在idea中,右擊

    再把對應的類拖進來就可以自動生成,
package com.cxyxs.designmode.factory.methodmodel;


/**
 * Description:寵物店
 * Author:程序猿學社
 * Date:  2020/4/3 23:16
 * Modified By:
 */
public interface PetShop {
    void buy();
}

class  Meat implements PetShop {

    @Override
    public void buy() {
        System.out.println("社長給湯圓購買肉乾");
    }
}

class  Foot implements PetShop {

    @Override
    public void buy() {
        System.out.println("社長給湯圓購買貓糧");
    }
}


interface  Factory{
    PetShop food();
}

class   MeatFactory implements  Factory{

    @Override
    public PetShop food() {
        return new Meat();
    }
}

class  FootFactory implements  Factory{

    @Override
    public PetShop food() {
        return new Foot();
    }
}



/**
 * 提供者代碼
 * =======================
 * 調用端代碼
 */
class  Test{
    public static void main(String[] args) {
        Factory factory = new MeatFactory();
        FootFactory footFactory = new FootFactory();

        PetShop food = factory.food();
        PetShop food1 = footFactory.food();

        food.buy();
        food1.buy();
    }
}


隔壁老王: “社長,你這代碼,我沒有看出什麼好處來,只知道你這代碼越來越複雜,繞的頭都暈咯。”
社長: “老王,別急呀,我們之前已經知道簡單工廠是不符合開閉原則的,也就是說,儘量不要在已經的代碼上進行修改,應該進行擴展”
社長: “我家的湯圓現在已經不滿足於肉乾和貓糧咯,需要喫魚,我們看看,我們如何在現有的代碼上進行改動”

/**
 * 擴展喫魚的部分開頭
 */
class  Fish implements PetShop {

    @Override
    public void buy() {
        System.out.println("社長給湯圓購買小魚魚");
    }
}
class FishFactory implements  Factory{

    @Override
    public PetShop food() {
        return new Fish() ;
    }
}

/**
 * 提供者代碼
 * =======================
 * 調用端代碼
 */
class  Test{
    public static void main(String[] args) {
        Factory factory = new MeatFactory();
        FootFactory footFactory = new FootFactory();

        Factory fishFactory = new FishFactory();

        PetShop food = factory.food();
        PetShop food1 = footFactory.food();
        PetShop food2 = fishFactory.food();

        food.buy();
        food1.buy();
        food2.buy();
    }
}
  • 擴展產品,具體工廠也跟着擴展,不需要修改以前的代碼,遵守了開閉原則。

好處:

  • 提供者修改代碼後,調用者是不知道的,迪米特法則,也就是最少知道原則。
  • 在簡單工廠上做咯優化,擴展產品,不需要修改以前的代碼,只需要擴展一個產品和一個具體工廠即可

隔壁老王: “就拿你上面main方法裏面的MeatFactory舉例,假設MeatFactory類變爲MeatFactory123,還是需要修改提供者和調用者兩邊的代碼”
社長 “工廠名稱有一套規範,只需要提供者保證儘量不改動類名就行,不然就是一個死循環,看看mybatis工廠類,版本變動,工廠名也不會變動。mybatis開發就相當於提供者,我們使用人員,就相當於調用者,如果mybatis工廠名變動,我們開發也不知道,這體驗是不是很不好。所以,這個問題,不用擔心,都有規範的”

抽象工廠模式(第三種)

社長: “我們之前只是很簡單的實現喫,貓還會喫、睡等行爲(多個產品等級)。產品等級一多,工廠類就會變得越來越臃腫”

使用工廠方法模式

  • 需要使用到12個類,工廠的涉及類就有6個
package com.cxyxs.designmode.factory.abstrastinterface;

/**
 * Description:
 * Author: 程序猿學社
 * Date:  2020/4/4 18:20
 * Modified By:
 */
public interface Foot {
    void eat();
}

class  Meat implements  Foot{

    @Override
    public void eat() {
        System.out.println("給湯圓喫肉乾");
    }
}

class  Fish  implements  Foot{

    @Override
    public void eat() {
        System.out.println("給湯圓喫小魚仔");
    }
}

interface Toy{
    void play();
}

class  CatTeaser implements Toy{

    @Override
    public void play() {
        System.out.println("社長利用逗貓棒跟湯圓玩耍");
    }
}

class Ball implements  Toy{

    @Override
    public void play() {
        System.out.println("湯圓一個人跟小球進行玩耍");
    }
}

/**
 * 食物工廠代碼
 */
interface FootFactory{
    public Foot proFoot();
}
class MeatFactory implements  FootFactory{

    @Override
    public Foot proFoot() {
        return new Meat();
    }
}

class FishFactory implements  FootFactory{

    @Override
    public Foot proFoot() {
        return new Fish();
    }
}

/**
 * 玩具工廠
 */
interface ToyFactory{
    public Toy proToy();
}
class  CatTeaserFactory implements  ToyFactory{

    @Override
    public Toy proToy() {
        return new CatTeaser();
    }
}

class  BallFactory implements  ToyFactory{

    @Override
    public Toy proToy() {
        return new Ball();
    }
}

使用抽象工廠實現

社長: “先看看類圖,再根據類圖實現對應的代碼”

package com.cxyxs.designmode.factory.abstrastinterface.plus;

/**
 * Description:
 * Author: 程序猿學社
 * Date:  2020/4/4 18:20
 * Modified By:
 */
public interface Foot {
    void eat();
}

class  Meat implements Foot {

    @Override
    public void eat() {
        System.out.println("給湯圓喫肉乾");
    }
}

class  Fish  implements Foot {

    @Override
    public void eat() {
        System.out.println("給湯圓喫小魚仔");
    }
}

interface Toy{
    void play();
}

class  CatTeaser implements Toy {

    @Override
    public void play() {
        System.out.println("社長利用逗貓棒跟湯圓玩耍");
    }
}

class Ball implements Toy {

    @Override
    public void play() {
        System.out.println("湯圓一個人跟小球進行玩耍");
    }
}

/**
 * 食物工廠代碼
 */
interface Factory{
    public Foot proFoot();
    public Toy proToy();
}

class MeatAndCatTeaserFactory implements Factory {

    @Override
    public Foot proFoot() {
        return new Meat();
    }

    @Override
    public Toy proToy() {
        return new CatTeaser();
    }
}

class FishAndBallFactory implements  Factory{

    @Override
    public Foot proFoot() {
        return new Fish();
    }

    @Override
    public Toy proToy() {
        return new Ball();
    }
}
  • 工廠類由之前的6個,變爲3個。減少了工廠類的臃腫

優點:

  • 抽象工廠可以理解爲簡單工廠和工廠方法模式的一個彙總
  • 對產品進行了抽象,適合一些維度有關聯的(也就是說,有邏輯關係),例如,我舉的這個例子,在工廠方法中,會有喫的工廠和玩的工廠,而在抽象工廠中,直接把這兩個具體工廠直接抽象出來,合二爲一。

缺點:

  • 要求抽象的多個產品,之前有邏輯關係
  • 後續需要生產喝的東西,需要修改抽象工廠,同時各個具體工廠也需要修改

隔壁老王: “社長,在抽象工廠模式中,喫和玩都是捆綁的關係,你這是捆綁銷售,如果,我只想要實現喫,應該怎麼辦?”
社長: “如果只想實現喫,就可以使用工廠方法模式,應該根據具體問題,具體選擇,使用那個模式。”

總結:

  • 不管是簡單工廠、工廠方法模式、抽象工廠模式,我們應該靈活運用,主要的目的,還是爲了解耦,寫出可擴展性、可讀的代碼。

原創不易,不要白嫖,覺得有用的社友,給我點贊,讓更多的老鐵看到這篇文章。
因技術能力有限,如文中有不合理的地方,希望各位大佬指出,在下方評論留言,謝謝,希望大家一起進步,一起成長。

作者:程序猿學社
原創公衆號:『程序猿學社』,專注於java技術棧,分享java各個技術系列專題,以及各個技術點的面試題。
原創不易,轉載請註明來源(註明:來源於公衆號:程序猿學社, 作者:程序猿學社)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章