Java設(shè)計(jì)模式之工廠(chǎng)模式的三種實(shí)現(xiàn)方式對(duì)比

工廠(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)型困難。

Java設(shè)計(jì)模式之工廠(chǎng)模式的三種實(shí)現(xiàn)方式對(duì)比

工廠(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)景。

Java設(shè)計(jì)模式之工廠(chǎng)模式的三種實(shí)現(xiàn)方式對(duì)比

解決方案

工廠(chǎng)模式,顧名思義,就是像一個(gè)工廠(chǎng)一樣,負(fù)責(zé)“生產(chǎn)”對(duì)象。這三種模式在實(shí)現(xiàn)上各有千秋,但都圍繞著“解耦創(chuàng)建邏輯”這個(gè)核心思想。

Java設(shè)計(jì)模式之工廠(chǎng)模式的三種實(shí)現(xiàn)方式對(duì)比

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ì)象。

Java設(shè)計(jì)模式之工廠(chǎng)模式的三種實(shí)現(xià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):
    • 違背開(kāi)閉原則(Open/Closed Principle): 每增加一種新產(chǎn)品,都需要修改工廠(chǎng)類(lèi)的創(chuàng)建邏輯(通常是if-else或switch-case語(yǔ)句),這會(huì)導(dǎo)致工廠(chǎng)類(lèi)變得臃腫且難以維護(hù)。
    • 職責(zé)過(guò)重: 工廠(chǎng)類(lèi)承擔(dān)了所有產(chǎn)品的創(chuàng)建職責(zé)。
  • 適用場(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)題的工具,不是炫技的手段。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊6 分享