設計模式之工廠模式,史上最強,不服來辯!

設計模式是對大家實際工作中寫的各種代碼進行高層次抽象的總結,如果設計模式沒學會,抽象能力肯定就不會太強。常見的設計模式有 23 種,今天我們只聊最簡單的工廠模式。

工廠模式是屬於創建型模式的,通過工廠獲取一個一個的新對象。說白了,工廠就是用來 new(創建)對象的,因此把它化分到創建型這一類中。

簡單工廠模式

簡單工廠模式,正如其名,和名字一樣簡單,非常簡單。下面先看一段代碼:

public class FoodFactory {
    public static Food makeFood(String name) {
        if ("kuaican".equals(name)) {
            Food kuaican = new KuaiCanFood();
            kuaican.setPrice(20.00);
            return kuaican;
        } 

        if ("hamburger".equals(name)) {
            Food hamburger = new HamburgerFood();
            hamburger.setPrice(22.00);
            hamburger.setMeat("beef");
            return hamburger;
        } 

        // ......

        return new RuntimeException("not food");
    }
}

需要注意的是,上面中的 KuaiCanFood 和 HamburgerFood 都繼承自 Food。

簡單地說,簡單工廠模式通常就是這樣,一個工廠類 XxxFactory,裏面有一個靜態方法,根據我們不同的參數,返回不同的派生自同一個父類(或實現同一接口)的實例對象。

我們強調職責單一原則,一個類只提供一種功能,FoodFactory 的功能就是隻要負責生產各種 Food。

Java 各種框架中,簡單工廠模式是非常常見的,比如JedisConnectionFactory

Redis 連接工廠

基本上 Java 中需要創建連接的框架,都是用了工廠模式。

工廠模式

簡單工廠模式很簡單,在一般情況下,它都能滿足我們的需要。但是,在某些特殊場景,它就滿足不了我們的需求了。還是拿 Redis 爲例,Redis 有單機模式,主從模式,哨兵模式,Cluster 模式。每種模式的連接都不一樣,因此我們需要引入多個工廠。

這種情況下,就需要引入多個簡單工廠模式。比如:JedisConnectionFactoryJredisConnectionFactoryLettuceConnectionFactorySrpConnectionFactory等等。之所以需要引入工廠模式,是因爲我們往往需要使用兩個或兩個以上的工廠。

還是以 Food 爲例。

public interface FoodFactory {
    Food makeFood(String name);
}
public class ChineseFoodFactory implements FoodFactory {
    @Override
    public Food makeFood(String name) {
        if ("套餐A".equals(name)) {
            Food kuaican = new KuaiCanFood();
            kuaican.setPrice(20.00);
            kuaican.setName("快餐");
            return kuaican;
        } 
        
        if ("套餐B".equals(name)) {
            Food mifan = new MiFanFood();
            mifan.setPrice(18.00);
            mifan.setName("五常大米");
            return mifan;
        } 
        
        return null;
    }
}
public class AmericanFoodFactory implements FoodFactory {
    @Override
    public Food makeFood(String name) {
        if ("套餐A".equals(name)) {
            Food hamburger = new HamburgerFood();
            hamburger.setPrice(22.00);
            hamburger.setName("hamburger");
            return hamburger;
        } 
        
        if ("套餐B".equals(name)) {
            Food sandwich = new SandwichFood();
            sandwich.setPrice(25.00);
            sandwich.setName("三明治");
            return sandwich;
        } 
        
        return null;
    }
}

其中,KuaiCanFood、MiFanFood、HamburgerFood、SandwichFood 都派生自 Food。

消費者調用:

public class Consumer {
    public static void main(String[] args) {
        // 先選擇一個具體的工廠
        FoodFactory factory = new ChineseFoodFactory();
        // 由選擇的工廠產生具體的對象,不同的工廠造出不一樣的對象
        Food food = factory.makeFood("套餐A");
    }
}

雖然都是調用 makeFood("套餐 A")  製作套餐 A,但是,不同的工廠生產出來的完全不一樣。

第一步,我們需要選取合適的工廠,然後第二步基本上和簡單工廠一樣。

核心在於,我們需要在第一步選好我們需要的工廠。比如,我們有 FileFactory 接口,實現類有 AliyunOssFactory 和 TencentCosFactory,分別對應將文件寫入阿里雲 OSS 和騰訊雲 COS 中。很顯然,我們客戶端或者消費者第一步就需要決定到底要實例化 AliyunOssFactory 還是 TencentCosFactory,這將決定之後的所有的操作。

工廠模式非常簡單,我把他們都畫到一張圖上,希望大家能夠看圖就會:

工廠模式

現在會了還不是真會,一定要在實踐代碼中使用,不然過不了多久就會忘記!

抽象工廠模式

有了簡單工廠模式,和工廠模式,爲什麼還有抽象工廠模式?

這就是當涉及到產品族的時候,就需要引入抽象工廠模式了。當一個產品族,存在多個不同類型的產品 (比如我上面列舉的 Redis 連接工廠) 情況下,不同架構模式,選擇不同的連接工廠的問題。而這種場景在業務開發中也是非常多見的,只不過可能有時候沒有將它們抽象化出來。

Redis 的連接工廠

RedisConnectionFactory的例子,我就不在多說了,建議spring-data-redis中的源碼多看幾遍。下面我再列舉一個生活中的經典的例子,就是造一臺手機。我們先不引入抽象工廠模式,看看怎麼實現。

因爲手機是由許多的構件組成的,我們將處理器 CPU 和主板 Board 進行抽象,然後 CPU 由 CpuFactory 生產,主板由 MainBoardFactory 生產。然後,我們再將 CPU 和主板搭配起來組合在一起,如下圖:

抽象工廠模式

這個時候的如果需要手機產品,只需這樣調用:

public class Phone {
    private Cpu cpu;
    private MainBoard mainBoard;
    public Phone(Cpu cpu, MainBoard mainBoard){
        this.cpu = cpu;
        this.mainBoard = mainBoard;
    }
    public static void main(String[] args) {
        // 得到華爲的處理器
        CpuFactory KirinFactory = new HuaweiCpuFactory();
        Cpu cpu = KirinFactory.getCpu();
        // 得到華爲的主板
        MainBoardFactory mainBoardFactory = new HuaweiMainBoardFactory();
        MainBoard mainBoard = mainBoardFactory.getMainBoard();
        // 組裝手機CPU和主板
        Phone huaweiMate = new Phone(cpu, mainBoard);
    }
}

單獨看處理器 CPU 工廠和主板工廠,它們分別是前面我們說的工廠模式。這種方式也容易擴展,因爲要給手機加配件的話,只需要加一個 XxxFactory 和相應的實現即可,不需要修改現有的工廠。

但是,這種方式有一個問題,那就是如果蘋果🍎家產的 CPU 和華爲產的主板不能兼容使用,因此不能出現隨意組合。這就是產品族的概念,它代表了組成某個產品的一系列配件的集合。

當涉及到這種產品族的問題的時候,就需要抽象工廠模式來支持了。我們不再定義 CPU 工廠、主板工廠、OS 工廠、顯示屏工廠等等,我們直接定義手機工廠,每個手機工廠負責生產所有的設備,這樣能保證肯定不存在兼容問題。

public interface PhoneFactory {
    Cpu getCpu();
    MainBoard getMainBord();
    Display getDisplay();
}
public class HuaweiPhoneFactory implements PhoneFactory {
    @Override
    public Cpu getCpu() {
        return new HuaweiCpu();
    }
    @Override
    public MainBoard getMainBord() {
        return new HuaweiMainBoard();
    }
    @Override
    public Display getDisplay() {
        return new BoeDisplay();
    }
}
public class XiaomiPhoneFactory implements PhoneFactory {
    @Override
    public Cpu getCpu() {
        return new HuaweiCpu();
    }
    @Override
    public MainBoard getMainBord() {
        return new XiaomiMainBoard();
    }
    @Override
    public Display getDisplay() {
        return new VisionoxDisplay();
    }
}

這種情況下,對於生產來說,不再需要單獨挑選 CPU 廠商、顯示屏廠商等,直接選擇一家品牌工廠,品牌工廠會負責生產所有的東西,而且能保證肯定是兼容可用的。

public class Test {
    public static void main(String[] args) {
        // 第一步就要選定一個“大品牌工廠”
        PhoneFactory factory = new HuaweiPhoneFactory();
        // 從這個大廠設計製造CPU
        Cpu cpu = factory.getCpu();
        // 從這個大廠生產手機主板
        MainBoard board = factory.getMainBord();
        // 從這個大廠生產手機顯示屏
        Display boeDisplay = factory.getDisplay();
        // 生產一個華爲 Mete 40 手機
        Phone huaweiMete40 = new Phone(cpu, board, boeDisplay);
    }
}

這樣一個抽象工廠的代碼和案例就講完了,如果還有不懂的,我下次再拿我們現在生產項目中的案例給大家講解!

最後總結一下,功能模式根據需求和功能的不同,你可以選擇對應的簡單工廠,普通工廠,和抽象工廠。抽象工廠看起來比較抽象,它暴露的問題也是顯而易見的,比如我們要加個 NFC 模塊,就需要修改所有的工廠,給所有的工廠都加上製造顯示器的方法。這有點違反了對修改關閉,對擴展開放這個設計原則。但也不要完全照搬設計原則,畢竟有時候是需要打破原則,不是嗎?比如在電商系統中的一些反範式設計等。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/H_kb_BObQVkTYcrF6orkPw