設計模式之工廠模式,史上最強,不服來辯!
設計模式是對大家實際工作中寫的各種代碼進行高層次抽象的總結,如果設計模式沒學會,抽象能力肯定就不會太強。常見的設計模式有 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 模式。每種模式的連接都不一樣,因此我們需要引入多個工廠。
這種情況下,就需要引入多個簡單工廠模式。比如:JedisConnectionFactory
、JredisConnectionFactory
、LettuceConnectionFactory
、SrpConnectionFactory
等等。之所以需要引入工廠模式,是因爲我們往往需要使用兩個或兩個以上的工廠。
還是以 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