工廠(chǎng)模式的核心目的是封裝對(duì)象創(chuàng)建過(guò)程,解耦創(chuàng)建與使用,提升靈活性和可維護(hù)性,主要有三種實(shí)現(xiàn)方式:1. 簡(jiǎn)單工廠(chǎng)由一個(gè)工廠(chǎng)類(lèi)根據(jù)參數(shù)創(chuàng)建所有產(chǎn)品,適用于產(chǎn)品種類(lèi)少且穩(wěn)定的場(chǎng)景,但違背開(kāi)閉原則;2. 工廠(chǎng)方法通過(guò)抽象工廠(chǎng)接口讓子類(lèi)決定創(chuàng)建哪個(gè)產(chǎn)品,符合開(kāi)閉原則,適合產(chǎn)品類(lèi)型多且需擴(kuò)展的場(chǎng)景,但類(lèi)數(shù)量增加;3. 抽象工廠(chǎng)用于創(chuàng)建一組相關(guān)或依賴(lài)的產(chǎn)品族,適合跨平臺(tái)或主題切換等場(chǎng)景,但結(jié)構(gòu)復(fù)雜且擴(kuò)展新產(chǎn)品類(lèi)型困難。
工廠(chǎng)模式在Java設(shè)計(jì)模式中,核心目的都是為了封裝對(duì)象的創(chuàng)建過(guò)程,從而將對(duì)象的創(chuàng)建與使用分離,降低代碼的耦合度,提升系統(tǒng)的靈活性和可維護(hù)性。它主要有三種常見(jiàn)的實(shí)現(xiàn)方式:簡(jiǎn)單工廠(chǎng)(Simple Factory)、工廠(chǎng)方法(Factory Method)和抽象工廠(chǎng)(Abstract Factory),它們各有側(cè)重,適用于不同的場(chǎng)景。
解決方案
工廠(chǎng)模式,顧名思義,就是像一個(gè)工廠(chǎng)一樣,負(fù)責(zé)“生產(chǎn)”對(duì)象。這三種模式在實(shí)現(xiàn)上各有千秋,但都圍繞著“解耦創(chuàng)建邏輯”這個(gè)核心思想。
1. 簡(jiǎn)單工廠(chǎng)模式(Simple Factory Pattern)
立即學(xué)習(xí)“Java免費(fèi)學(xué)習(xí)筆記(深入)”;
這可能是我們?nèi)粘>幋a中最容易想到的,也是最“不那么”像設(shè)計(jì)模式的設(shè)計(jì)模式。它通常包含一個(gè)工廠(chǎng)類(lèi),這個(gè)類(lèi)里有一個(gè)靜態(tài)方法,根據(jù)傳入的參數(shù)來(lái)決定創(chuàng)建并返回哪種具體的產(chǎn)品對(duì)象。
- 核心思想: 由一個(gè)工廠(chǎng)類(lèi)負(fù)責(zé)所有產(chǎn)品的創(chuàng)建。
- 優(yōu)點(diǎn):
- 簡(jiǎn)單易用: 代碼量少,理解成本低,對(duì)于少量產(chǎn)品類(lèi)型非常方便。
- 集中管理: 所有的創(chuàng)建邏輯都集中在一個(gè)地方,方便修改。
- 缺點(diǎn):
- 適用場(chǎng)景: 產(chǎn)品種類(lèi)較少且相對(duì)穩(wěn)定,或者只是為了簡(jiǎn)單地封裝創(chuàng)建過(guò)程,不想引入過(guò)多復(fù)雜度的場(chǎng)景。
// 產(chǎn)品接口 interface Product { void use(); } // 具體產(chǎn)品A class ConcreteProductA implements Product { @Override public void use() { System.out.println("使用產(chǎn)品A"); } } // 具體產(chǎn)品B class ConcreteProductB implements Product { @Override public void use() { System.out.println("使用產(chǎn)品B"); } } // 簡(jiǎn)單工廠(chǎng) class SimpleProductFactory { public static Product createProduct(String type) { if ("A".equalsIgnoreCase(type)) { return new ConcreteProductA(); } else if ("B".equalsIgnoreCase(type)) { return new ConcreteProductB(); } else { throw new IllegalArgumentException("未知產(chǎn)品類(lèi)型: " + type); } } } // 客戶(hù)端使用 public class Client { public static void main(String[] args) { Product productA = SimpleProductFactory.createProduct("A"); productA.use(); // 輸出: 使用產(chǎn)品A Product productB = SimpleProductFactory.createProduct("B"); productB.use(); // 輸出: 使用產(chǎn)品B } }
2. 工廠(chǎng)方法模式(Factory Method Pattern)
這是為了解決簡(jiǎn)單工廠(chǎng)模式中違反開(kāi)閉原則的問(wèn)題而誕生的。它將產(chǎn)品對(duì)象的創(chuàng)建延遲到子類(lèi)工廠(chǎng)中。每個(gè)具體產(chǎn)品都對(duì)應(yīng)一個(gè)具體的工廠(chǎng)類(lèi)。
- 核心思想: 定義一個(gè)用于創(chuàng)建對(duì)象的接口,讓子類(lèi)決定實(shí)例化哪一個(gè)類(lèi)。工廠(chǎng)方法使一個(gè)類(lèi)的實(shí)例化延遲到其子類(lèi)。
- 優(yōu)點(diǎn):
- 符合開(kāi)閉原則: 增加新產(chǎn)品時(shí),只需增加新的具體產(chǎn)品類(lèi)和對(duì)應(yīng)的具體工廠(chǎng)類(lèi),無(wú)需修改現(xiàn)有代碼。
- 職責(zé)單一: 每個(gè)工廠(chǎng)只負(fù)責(zé)創(chuàng)建一種產(chǎn)品。
- 客戶(hù)端與具體產(chǎn)品解耦: 客戶(hù)端只與抽象工廠(chǎng)和抽象產(chǎn)品交互。
- 缺點(diǎn):
- 類(lèi)數(shù)量增加: 每增加一個(gè)產(chǎn)品,就需要增加一個(gè)對(duì)應(yīng)的工廠(chǎng)類(lèi),導(dǎo)致類(lèi)的數(shù)量膨脹,增加了系統(tǒng)的復(fù)雜性。
- 適用場(chǎng)景: 當(dāng)一個(gè)類(lèi)不知道它所需要的對(duì)象的類(lèi)時(shí);當(dāng)一個(gè)類(lèi)希望它的子類(lèi)來(lái)指定它所創(chuàng)建的對(duì)象時(shí)。這是最常用的一種工廠(chǎng)模式。
// 產(chǎn)品接口 (同上) // interface Product { void use(); } // class ConcreteProductA implements Product { ... } // class ConcreteProductB implements Product { ... } // 抽象工廠(chǎng)接口 interface ProductFactory { Product createProduct(); } // 具體產(chǎn)品A的工廠(chǎng) class ConcreteProductAFactory implements ProductFactory { @Override public Product createProduct() { return new ConcreteProductA(); } } // 具體產(chǎn)品B的工廠(chǎng) class ConcreteProductBFactory implements ProductFactory { @Override public Product createProduct() { return new ConcreteProductB(); } } // 客戶(hù)端使用 public class Client { public static void main(String[] args) { ProductFactory factoryA = new ConcreteProductAFactory(); Product productA = factoryA.createProduct(); productA.use(); // 輸出: 使用產(chǎn)品A ProductFactory factoryB = new ConcreteProductBFactory(); Product productB = factoryB.createProduct(); productB.use(); // 輸出: 使用產(chǎn)品B } }
3. 抽象工廠(chǎng)模式(Abstract Factory Pattern)
這是工廠(chǎng)模式中最復(fù)雜的一種,它不僅創(chuàng)建單個(gè)產(chǎn)品,而是創(chuàng)建一系列相關(guān)或相互依賴(lài)的產(chǎn)品對(duì)象,而無(wú)需指定它們具體的類(lèi)。
- 核心思想: 提供一個(gè)接口,用于創(chuàng)建相關(guān)或依賴(lài)對(duì)象的家族,而無(wú)需明確指定具體類(lèi)。
- 優(yōu)點(diǎn):
- 提供一個(gè)產(chǎn)品族的抽象接口: 確??蛻?hù)端使用一個(gè)產(chǎn)品族中的多個(gè)對(duì)象是相互兼容的。
- 客戶(hù)端與具體工廠(chǎng)和產(chǎn)品解耦: 客戶(hù)端完全不知道具體的產(chǎn)品和工廠(chǎng)實(shí)現(xiàn)。
- 易于切換產(chǎn)品族: 改變一個(gè)產(chǎn)品族的實(shí)現(xiàn),只需切換具體的工廠(chǎng)即可。
- 缺點(diǎn):
- 增加復(fù)雜性: 引入了更多的接口和類(lèi),理解和維護(hù)成本更高。
- 難以擴(kuò)展新產(chǎn)品類(lèi)型: 如果要增加新的產(chǎn)品類(lèi)型(而不是新的產(chǎn)品族),需要修改所有的抽象工廠(chǎng)和具體工廠(chǎng),違反開(kāi)閉原則。
- 適用場(chǎng)景: 當(dāng)需要?jiǎng)?chuàng)建一組相關(guān)或相互依賴(lài)的對(duì)象時(shí);當(dāng)系統(tǒng)需要獨(dú)立于這些對(duì)象的創(chuàng)建、組合和表示時(shí)。比如,一個(gè)ui庫(kù)需要支持多種操作系統(tǒng)主題(windows風(fēng)格、Mac風(fēng)格),每種主題下有按鈕、文本框等控件,這些控件在同一主題下是相互兼容的。
// 抽象產(chǎn)品A接口 interface AbstractProductA { void showA(); } // 抽象產(chǎn)品B接口 interface AbstractProductB { void showB(); } // 具體產(chǎn)品A1 class ConcreteProductA1 implements AbstractProductA { @Override public void showA() { System.out.println("這是產(chǎn)品A1"); } } // 具體產(chǎn)品A2 class ConcreteProductA2 implements AbstractProductA { @Override public void showA() { System.out.println("這是產(chǎn)品A2"); } } // 具體產(chǎn)品B1 class ConcreteProductB1 implements AbstractProductB { @Override public void showB() { System.out.println("這是產(chǎn)品B1"); } } // 具體產(chǎn)品B2 class ConcreteProductB2 implements AbstractProductB { @Override public void showB() { System.out.println("這是產(chǎn)品B2"); } } // 抽象工廠(chǎng)接口 interface AbstractFactory { AbstractProductA createProductA(); AbstractProductB createProductB(); } // 具體工廠(chǎng)1,生產(chǎn)產(chǎn)品A1和B1 class ConcreteFactory1 implements AbstractFactory { @Override public AbstractProductA createProductA() { return new ConcreteProductA1(); } @Override public AbstractProductB createProductB() { return new ConcreteProductB1(); } } // 具體工廠(chǎng)2,生產(chǎn)產(chǎn)品A2和B2 class ConcreteFactory2 implements AbstractFactory { @Override public AbstractProductA createProductA() { return new ConcreteProductA2(); } @Override public AbstractProductB createProductB() { return new ConcreteProductB2(); } } // 客戶(hù)端使用 public class Client { public static void main(String[] args) { AbstractFactory factory1 = new ConcreteFactory1(); AbstractProductA productA1 = factory1.createProductA(); AbstractProductB productB1 = factory1.createProductB(); productA1.showA(); // 輸出: 這是產(chǎn)品A1 productB1.showB(); // 輸出: 這是產(chǎn)品B1 AbstractFactory factory2 = new ConcreteFactory2(); AbstractProductA productA2 = factory2.createProductA(); AbstractProductB productB2 = factory2.createProductB(); productA2.showA(); // 輸出: 這是產(chǎn)品A2 productB2.showB(); // 輸出: 這是產(chǎn)品B2 } }
為什么我們需要工廠(chǎng)模式?
說(shuō)實(shí)話(huà),剛接觸設(shè)計(jì)模式的時(shí)候,我個(gè)人覺(jué)得工廠(chǎng)模式有點(diǎn)“小題大做”。不就是new一個(gè)對(duì)象嗎?直接new不香嗎?但隨著項(xiàng)目規(guī)模的擴(kuò)大,我逐漸意識(shí)到,直接new帶來(lái)的問(wèn)題遠(yuǎn)比想象中要多。這背后其實(shí)是對(duì)“依賴(lài)”的深刻理解。
我們直接new一個(gè)對(duì)象時(shí),比如Product p = new ConcreteProductA();,我們的代碼就直接依賴(lài)于ConcreteProductA這個(gè)具體的類(lèi)。如果哪天ConcreteProductA的構(gòu)造函數(shù)變了,或者我們需要換成ConcreteProductB,那么所有直接new的地方都得改。這在大型項(xiàng)目中簡(jiǎn)直是災(zāi)難。
工廠(chǎng)模式的核心價(jià)值,就在于它把“誰(shuí)來(lái)創(chuàng)建對(duì)象”這個(gè)職責(zé)給抽象并封裝起來(lái)了??蛻?hù)端代碼不再直接關(guān)心具體產(chǎn)品是如何被創(chuàng)建的,它只關(guān)心抽象的產(chǎn)品接口。這樣一來(lái),我們的業(yè)務(wù)邏輯代碼就能專(zhuān)注于處理業(yè)務(wù)本身,而不用被底層的對(duì)象創(chuàng)建細(xì)節(jié)所干擾。這大大提升了代碼的解耦性和可維護(hù)性。想象一下,如果你的系統(tǒng)需要支持多種數(shù)據(jù)庫(kù),直接new mysqlConnection、new oracleConnection,那改起來(lái)簡(jiǎn)直要命。但如果通過(guò)工廠(chǎng)來(lái)獲取連接,切換就變得輕而易舉。
三種工廠(chǎng)模式的適用場(chǎng)景與權(quán)衡
我們已經(jīng)看到了這三種模式的實(shí)現(xiàn)方式,但更重要的是,什么時(shí)候該用哪個(gè)?這其實(shí)是一個(gè)權(quán)衡的過(guò)程,沒(méi)有絕對(duì)的“最好”,只有最適合當(dāng)前上下文的。
簡(jiǎn)單工廠(chǎng),我通常把它看作是“初級(jí)封裝”。它最簡(jiǎn)單,上手快,如果你只是想把一些散亂的new操作集中起來(lái),或者你的產(chǎn)品類(lèi)型非常少,而且未來(lái)基本不會(huì)變動(dòng),那么簡(jiǎn)單工廠(chǎng)是你的不二之選。比如,一個(gè)工具類(lèi),根據(jù)傳入的字符串返回不同的解析器實(shí)例。它的缺點(diǎn)在于,一旦產(chǎn)品種類(lèi)增多,那個(gè)工廠(chǎng)方法里的if-else鏈條就會(huì)變得很長(zhǎng),每次新增產(chǎn)品都要修改它,這就違反了開(kāi)閉原則,維護(hù)起來(lái)會(huì)很頭疼。
工廠(chǎng)方法,在我看來(lái),是工廠(chǎng)模式的“主力軍”。它解決了簡(jiǎn)單工廠(chǎng)的擴(kuò)展性問(wèn)題。當(dāng)你預(yù)計(jì)系統(tǒng)會(huì)不斷增加新的產(chǎn)品類(lèi)型時(shí),工廠(chǎng)方法就顯得尤為重要。它通過(guò)引入抽象工廠(chǎng)和具體工廠(chǎng),將創(chuàng)建的職責(zé)下放到子類(lèi),完美地符合了開(kāi)閉原則。每次新增產(chǎn)品,你只需要添加一個(gè)新的具體產(chǎn)品類(lèi)和一個(gè)新的具體工廠(chǎng)類(lèi),對(duì)現(xiàn)有代碼幾乎沒(méi)有影響。它的代價(jià)是類(lèi)文件數(shù)量會(huì)顯著增加。對(duì)于一個(gè)產(chǎn)品,你可能需要一個(gè)產(chǎn)品接口、一個(gè)具體產(chǎn)品類(lèi)、一個(gè)工廠(chǎng)接口、一個(gè)具體工廠(chǎng)類(lèi),這一下子就多了四份代碼。但為了系統(tǒng)的可擴(kuò)展性,這種“臃腫”是值得的。
抽象工廠(chǎng),這是工廠(chǎng)模式中的“重型武器”,也是最復(fù)雜的。它不是為了創(chuàng)建單個(gè)產(chǎn)品,而是為了創(chuàng)建“產(chǎn)品族”。想象一下,你正在開(kāi)發(fā)一個(gè)跨平臺(tái)的GUI應(yīng)用,需要同時(shí)支持Windows和macos風(fēng)格的界面。Windows風(fēng)格的按鈕和文本框,與macos風(fēng)格的按鈕和文本框,它們各自形成一個(gè)“家族”。抽象工廠(chǎng)就能讓你在不修改客戶(hù)端代碼的情況下,輕松切換整個(gè)產(chǎn)品家族。它的缺點(diǎn)是顯而易見(jiàn)的:復(fù)雜性急劇上升。而且,如果你想新增一個(gè)產(chǎn)品“類(lèi)型”(比如,除了按鈕和文本框,現(xiàn)在還要增加一個(gè)滑塊),那么所有的抽象工廠(chǎng)和具體工廠(chǎng)都需要修改,這又是一個(gè)違反開(kāi)閉原則的地方。所以,除非你真的有“產(chǎn)品族”的需求,否則不要輕易使用抽象工廠(chǎng),過(guò)度設(shè)計(jì)帶來(lái)的麻煩可能比它解決的問(wèn)題還要多。
實(shí)踐中的選擇:何時(shí)從簡(jiǎn)單轉(zhuǎn)向復(fù)雜?
在我的實(shí)際開(kāi)發(fā)經(jīng)驗(yàn)中,一個(gè)很關(guān)鍵的點(diǎn)是不要過(guò)度設(shè)計(jì)。我個(gè)人傾向于在起步階段保持簡(jiǎn)單,但心里要清楚,一旦業(yè)務(wù)邏輯開(kāi)始膨脹,就得考慮升級(jí)了。
通常,我會(huì)從簡(jiǎn)單工廠(chǎng)開(kāi)始。如果項(xiàng)目初期產(chǎn)品類(lèi)型不多,或者需求變動(dòng)不大,簡(jiǎn)單工廠(chǎng)足夠滿(mǎn)足需求。它能快速搭建起創(chuàng)建邏輯的雛形,避免了最初就引入過(guò)多不必要的復(fù)雜性。
然而,當(dāng)你的產(chǎn)品列表開(kāi)始變得冗長(zhǎng),或者你發(fā)現(xiàn)自己頻繁地修改簡(jiǎn)單工廠(chǎng)中的if-else語(yǔ)句來(lái)添加新產(chǎn)品時(shí),這就是一個(gè)明確的信號(hào):你該升級(jí)到工廠(chǎng)方法了。這種升級(jí)通常是漸進(jìn)的,你可以先將最常變動(dòng)的產(chǎn)品類(lèi)型抽取出來(lái),用工廠(chǎng)方法來(lái)管理。一旦你開(kāi)始感受到簡(jiǎn)單工廠(chǎng)帶來(lái)的維護(hù)負(fù)擔(dān),工廠(chǎng)方法模式的引入就顯得水到渠成,它會(huì)讓你在產(chǎn)品擴(kuò)展時(shí)感到非常順暢。
至于抽象工廠(chǎng),它是一個(gè)更高級(jí)別的抽象,只有當(dāng)你面臨創(chuàng)建“產(chǎn)品族”的需求時(shí),才應(yīng)該考慮它。比如,你的系統(tǒng)需要同時(shí)支持多種主題、多種皮膚、或者不同系列的產(chǎn)品(如Intel系列CPU和AMD系列CPU,它們各自有一套主板、內(nèi)存等配套產(chǎn)品)。如果你的需求僅僅是創(chuàng)建單一類(lèi)型的產(chǎn)品,即使有很多種,工廠(chǎng)方法也足夠了。不要為了用設(shè)計(jì)模式而用設(shè)計(jì)模式,因?yàn)槌橄蠊S(chǎng)的復(fù)雜性是實(shí)實(shí)在在的,它會(huì)增加學(xué)習(xí)曲線(xiàn)和維護(hù)成本。
總結(jié)一下,我的選擇路徑是:簡(jiǎn)單工廠(chǎng) -> 工廠(chǎng)方法 -> 抽象工廠(chǎng)。從最簡(jiǎn)單的開(kāi)始,隨著業(yè)務(wù)復(fù)雜度的提升和對(duì)可擴(kuò)展性要求的增加,逐步引入更復(fù)雜的模式。這就像蓋房子,一開(kāi)始可能只需要一個(gè)簡(jiǎn)單的棚子,但隨著家庭成員的增加和生活需求的提升,你才需要考慮建造多層別墅,而不是一開(kāi)始就規(guī)劃一個(gè)宮殿。設(shè)計(jì)模式是解決問(wèn)題的工具,不是炫技的手段。