設計雜談-工廠模式

“工廠”模式在各種框架中非常常見,包括 MyBatis,它是一種創建對象的設計模式。使用工廠模式有很多好處,尤其是在復雜的框架中,它可以帶來更好的靈活性、可維護性和可配置性

讓我們以 MyBatis 為例,來理解工廠模式及其優點:

MyBatis 中的工廠:SqlSessionFactoryBuilderSqlSessionFactory

在 MyBatis 中,主要的工廠類是 SqlSessionFactoryBuilderSqlSessionFactory

  1. SqlSessionFactoryBuilder(構建器):

    • 它的作用是讀取 MyBatis 的配置文件(例如 mybatis-config.xml)或通過 Java 代碼構建 Configuration 對象Configuration 對象包含了 MyBatis 的所有配置信息,例如數據源、事務管理器、映射器等。
    • SqlSessionFactoryBuilder 的生命周期很短。一旦 SqlSessionFactory 被創建出來,SqlSessionFactoryBuilder 通常就不再需要了。你可以把它想象成一個“臨時的工廠的建造者”。
  2. SqlSessionFactory(會話工廠):

    • 它的作用是根據 Configuration 對象創建一個 SqlSession 對象SqlSession 是 MyBatis 中與數據庫交互的核心接口,通過它你可以執行 SQL 語句、管理事務等。
    • SqlSessionFactory 的生命周期通常是整個應用的生命周期。它是一個“持久的工廠”,負責生產 SqlSession

使用工廠模式的好處(以 MyBatis 為例):

  1. 封裝對象的創建過程:

    • 工廠模式將對象的創建邏輯封裝在一個或多個工廠類中。在 MyBatis 的例子中,創建 SqlSession 的復雜過程被封裝在 SqlSessionFactory 中。
    • 客戶端代碼(你的業務代碼)不需要知道創建 SqlSession 的具體細節,只需要從 SqlSessionFactory 獲取即可。這降低了客戶端代碼的復雜性。
  2. 解耦對象的創建和使用:

    • 工廠模式將對象的創建和使用分離。你的業務代碼依賴的是 SqlSession 接口,而 SqlSession 的具體實現是由 SqlSessionFactory 負責創建的。
    • 這種解耦使得在需要更換 SqlSession 的實現或者修改其創建方式時,你的業務代碼不需要做大的改動,只需要修改工廠的配置即可。
  3. 提高靈活性和可配置性:

    • 通過配置文件(mybatis-config.xml)或編程方式配置 SqlSessionFactoryBuilder,你可以靈活地指定 MyBatis 的各種行為,例如使用哪個數據源、事務管理器、是否開啟緩存等等。
    • SqlSessionFactory 會根據這些配置創建出具有相應特性的 SqlSession。這使得框架具有很高的可配置性。
  4. 隱藏對象的創建細節:

    • 工廠可以隱藏對象創建的復雜性,例如對象的初始化參數、依賴關系等。客戶端只需要簡單地向工廠請求對象,而不需要關心這些內部細節。
    • 在 MyBatis 中,SqlSessionFactory 負責處理數據源的創建、連接池的管理等復雜細節,客戶端只需要獲取 SqlSession 來執行 SQL。
  5. 控制對象的生命周期:

    • 工廠可以控制所創建對象的生命周期。例如,SqlSessionFactory 可以管理數據源和連接池的生命周期,而 SqlSession 的生命周期通常是請求級別的。
  6. 易于擴展和維護:

    • 當需要引入新的實現或者修改對象的創建邏輯時,只需要修改工廠類或其配置,而不需要修改所有使用該對象的客戶端代碼。這提高了框架的可擴展性和可維護性。

為了更直觀地理解工廠模式的優勢,我將提供一個簡單的場景,分別用不用工廠模式來實現,并對比它們的差異。

場景:創建不同類型的日志記錄器

假設我們需要根據配置創建不同類型的日志記錄器,目前有兩種:控制臺日志記錄器 (ConsoleLogger) 和文件日志記錄器 (FileLogger)。

1. 不使用工廠模式的實現

interface Logger {void log(String message);
}class ConsoleLogger implements Logger {@Overridepublic void log(String message) {System.out.println("[Console] " + message);}
}class FileLogger implements Logger {private String filePath;public FileLogger(String filePath) {this.filePath = filePath;// 初始化文件寫入器等...System.out.println("FileLogger initialized with path: " + filePath);}@Overridepublic void log(String message) {// 將消息寫入文件System.out.println("[File] " + message + " (written to " + filePath + ")");}
}public class LoggingServiceWithoutFactory {private String loggerType;private String fileLogPath;public LoggingServiceWithoutFactory(String loggerType, String fileLogPath) {this.loggerType = loggerType;this.fileLogPath = fileLogPath;}public Logger getLogger() {if ("console".equalsIgnoreCase(loggerType)) {return new ConsoleLogger();} else if ("file".equalsIgnoreCase(loggerType)) {return new FileLogger(fileLogPath);} else {throw new IllegalArgumentException("Unsupported logger type: " + loggerType);}}public void logMessage(String message) {Logger logger = getLogger();logger.log(message);}public static void main(String[] args) {LoggingServiceWithoutFactory consoleService = new LoggingServiceWithoutFactory("console", null);consoleService.logMessage("Log to console.");LoggingServiceWithoutFactory fileService = new LoggingServiceWithoutFactory("file", "app.log");fileService.logMessage("Log to file.");// 如果要添加新的日志記錄器類型,需要修改 LoggingServiceWithoutFactory}
}

缺點(不使用工廠模式):

  • 緊耦合: LoggingServiceWithoutFactory 類直接依賴于 ConsoleLoggerFileLogger 的具體實現。如果添加新的日志記錄器類型,你需要修改 getLogger() 方法。
  • 違反開閉原則: 對修改開放(需要修改 getLogger()),對擴展關閉(不容易在不修改現有代碼的情況下添加新的日志記錄器)。
  • 創建邏輯分散: 創建不同類型 Logger 的邏輯集中在一個 getLogger() 方法中,如果創建邏輯變得復雜,這個方法會變得難以維護。
  • 客戶端需要知道具體的類名: LoggingServiceWithoutFactory 的構造函數需要知道 loggerType 字符串,這間接暴露了具體的實現類名。

2. 使用工廠模式的實現

interface Logger {void log(String message);
}class ConsoleLogger implements Logger {@Overridepublic void log(String message) {System.out.println("[Console] " + message);}
}class FileLogger implements Logger {private String filePath;public FileLogger(String filePath) {this.filePath = filePath;System.out.println("FileLogger initialized with path: " + filePath);}@Overridepublic void log(String message) {System.out.println("[File] " + message + " (written to " + filePath + ")");}
}// 日志記錄器工廠接口
interface LoggerFactory {Logger createLogger();
}// 控制臺日志記錄器工廠
class ConsoleLoggerFactory implements LoggerFactory {@Overridepublic Logger createLogger() {return new ConsoleLogger();}
}// 文件日志記錄器工廠
class FileLoggerFactory implements LoggerFactory {private String filePath;public FileLoggerFactory(String filePath) {this.filePath = filePath;}@Overridepublic Logger createLogger() {return new FileLogger(filePath);}
}public class LoggingServiceWithFactory {private LoggerFactory loggerFactory;public LoggingServiceWithFactory(LoggerFactory loggerFactory) {this.loggerFactory = loggerFactory;}public void logMessage(String message) {Logger logger = loggerFactory.createLogger();logger.log(message);}public static void main(String[] args) {LoggerFactory consoleFactory = new ConsoleLoggerFactory();LoggingServiceWithFactory consoleService = new LoggingServiceWithFactory(consoleFactory);consoleService.logMessage("Log to console.");LoggerFactory fileFactory = new FileLoggerFactory("app.log");LoggingServiceWithFactory fileService = new LoggingServiceWithFactory(fileFactory);fileService.logMessage("Log to file.");// 要添加新的日志記錄器類型,只需要創建新的 Logger 和 LoggerFactory}
}

優點(使用工廠模式):

  • 解耦: LoggingServiceWithFactory 類依賴于 LoggerFactory 接口,而不是具體的 Logger 實現。具體的 Logger 對象的創建由相應的工廠負責。
  • 符合開閉原則: 要添加新的日志記錄器類型,你只需要創建新的 Logger 類和對應的 LoggerFactory 類,而不需要修改 LoggingServiceWithFactory 的代碼。
  • 職責分離: 對象創建的邏輯被委托給專門的工廠類,使得 LoggingServiceWithFactory 專注于日志記錄的服務邏輯。
  • 隱藏實現細節: LoggingServiceWithFactory 的構造函數接收 LoggerFactory 接口,不需要知道具體的 Logger 實現類名。
  • 更靈活的對象創建: 工廠可以包含更復雜的對象創建邏輯,例如讀取配置文件、依賴注入等。

對比總結:

特性不使用工廠模式使用工廠模式
耦合度高,直接依賴具體實現低,依賴抽象(接口)
開閉原則違反,添加新類型需要修改現有代碼符合,添加新類型只需創建新類
創建邏輯集中在 getLogger() 方法中分散在不同的工廠類中
靈活性較低,不易于擴展和修改較高,易于擴展和修改
客戶端依賴間接依賴具體實現類名依賴抽象工廠接口
維護性隨著類型的增加,getLogger() 方法變得難以維護每個工廠類職責單一,更易于維護

咱們用最簡單的大白話總結一下“工廠模式”是干啥的,以及為啥像 MyBatis 這樣的框架愛用它:

想象一下你要買不同口味的冰淇淋:

不用工廠模式就像這樣:

  • 你直接跑到冰柜前,自己翻箱倒柜地找你想要的口味(比如草莓味、巧克力味)。
  • 如果下次出了個新口味(比如抹茶味),你就得知道這個新口味的名字,然后自己去冰柜里找。
  • 如果冰淇淋的制作過程很復雜(比如要加很多配料、特殊冷凍),你買的時候也得稍微了解一下,不然可能買錯。

用工廠模式就像這樣:

  • 你不去冰柜里直接找,而是找到一個“冰淇淋工廠的售貨員”(這就是“工廠”)。
  • 你只需要告訴售貨員你想要什么口味(比如“草莓味”)。
  • 售貨員知道去哪里、怎么給你拿出正確的冰淇淋。
  • 如果出了新口味,你只需要告訴售貨員這個新口味的名字,售貨員自然會去工廠里幫你拿。
  • 你不需要知道冰淇淋是怎么做的,售貨員(工廠)幫你處理好了一切。

總結一下“工廠模式”:

  • 簡單來說: 就是專門找一個“家伙”(工廠)來幫你創建你需要的“東西”(對象),而不是你自己去直接創建。
  • 好處就像上面的冰淇淋例子:
    • 更省事: 你不用自己操心“東西”是怎么被創建出來的,交給工廠就行。
    • 更靈活: 如果想換一種“東西”或者創建“東西”的方式變了,你只需要告訴工廠,不用改你自己的用法。
    • 更好管理: 創建“東西”的邏輯都放在工廠里,管理起來更方便,不會亂糟糟地散在各處。

為啥像 MyBatis 這樣的框架愛用工廠模式?

MyBatis 需要創建很多跟數據庫打交道的“東西”(比如 SqlSession,就是用來執行 SQL 的)。創建這些“東西”可能挺復雜的,需要配置很多信息(連接哪個數據庫、用什么方式等等)。

用了“工廠模式”(SqlSessionFactory 就是個工廠),你的代碼就不用去管這些復雜的創建過程了,只需要跟工廠說“給我一個能干活的 SqlSession”,工廠就會根據它的配置幫你弄好。

這樣一來:

  • 你的代碼更干凈: 不用一堆創建 SqlSession 的復雜代碼。
  • MyBatis 更靈活: 如果你想換個數據庫或者改一下連接方式,只需要改一下 MyBatis 的配置(告訴工廠),你的代碼基本不用動。

LoggingServiceWithoutFactory 的構造函數需要知道 loggerType 字符串,這間接暴露了具體的實現類名。 為什么呢

LoggingServiceWithoutFactory 的構造函數中:

public LoggingServiceWithoutFactory(String loggerType, String fileLogPath) {this.loggerType = loggerType;this.fileLogPath = fileLogPath;
}

以及在 getLogger() 方法中:

public Logger getLogger() {if ("console".equalsIgnoreCase(loggerType)) {return new ConsoleLogger();} else if ("file".equalsIgnoreCase(loggerType)) {return new FileLogger(fileLogPath);} else {throw new IllegalArgumentException("Unsupported logger type: " + loggerType);}
}

為什么說構造函數需要知道 loggerType 字符串間接暴露了具體的實現類名?

  1. 字符串 loggerType 的含義: 傳遞給構造函數的 loggerType 字符串(例如 "console""file")并不是一個抽象的概念,而是直接對應著你希望創建的具體日志記錄器類的名稱(或其簡寫)。

  2. getLogger() 方法的邏輯: getLogger() 方法內部的 ifelse if 語句會根據 loggerType 字符串的值來硬編碼地創建具體的 Logger 實現類的實例 (new ConsoleLogger()new FileLogger(fileLogPath))。

  3. 客戶端代碼的依賴: 當客戶端代碼創建 LoggingServiceWithoutFactory 的實例時,它必須知道要使用哪個 loggerType 字符串,而這個字符串的選擇直接決定了最終會創建哪個具體的 Logger 實現類的對象。

    例如,在 main 方法中:

LoggingServiceWithoutFactory consoleService = new LoggingServiceWithoutFactory("console", null); // 客戶端需要知道 "console" 對應 ConsoleLogger
LoggingServiceWithoutFactory fileService = new LoggingServiceWithoutFactory("file", "app.log");   // 客戶端需要知道 "file" 對應 FileLogger
  • 這里,客戶端代碼需要使用字符串 "console" 來請求一個控制臺日志記錄器,使用字符串 "file" 來請求一個文件日志記錄器。這些字符串與具體的類名 ConsoleLoggerFileLogger 之間存在著直接的、雖然是通過字符串間接的關聯。

  • 修改的影響: 如果你想要添加一個新的日志記錄器類型(比如 DatabaseLogger),你需要修改 LoggingServiceWithoutFactorygetLogger() 方法,增加一個新的 else if 分支來創建 DatabaseLogger 的實例。同時,客戶端代碼也需要知道使用一個新的字符串(比如 "database")來請求這個新的日志記錄器。

對比使用工廠模式的情況:

在使用工廠模式的例子中,LoggingServiceWithFactory 的構造函數接收的是 LoggerFactory 接口:

public LoggingServiceWithFactory(LoggerFactory loggerFactory) {this.loggerFactory = loggerFactory;
}

客戶端代碼直接傳遞一個實現了 LoggerFactory 接口的具體工廠對象(例如 ConsoleLoggerFactoryFileLoggerFactory):

LoggerFactory consoleFactory = new ConsoleLoggerFactory();
LoggingServiceWithFactory consoleService = new LoggingServiceWithFactory(consoleFactory);LoggerFactory fileFactory = new FileLoggerFactory("app.log");
LoggingServiceWithFactory fileService = new LoggingServiceWithFactory(fileFactory);

在這里,LoggingServiceWithFactory 不直接依賴于具體的 Logger 實現類名,而是依賴于一個抽象的工廠接口。客戶端代碼雖然仍然需要知道具體的工廠類名,但 LoggingServiceWithFactory 本身與具體的 Logger 實現類解耦了。

總結:

在不使用工廠模式的例子中,loggerType 字符串充當了一個“配置標識符”,客戶端代碼通過這個標識符間接地告訴 LoggingServiceWithoutFactory 需要創建哪個具體的 Logger 實現類的對象。雖然沒有直接使用類名,但字符串的值與具體的類名之間存在著明確的映射關系,這仍然是一種形式的依賴,使得添加新的日志記錄器類型需要修改 LoggingServiceWithoutFactory 類的代碼。這就是為什么說構造函數需要知道 loggerType 字符串間接暴露了具體的實現類名。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/80750.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/80750.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/80750.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

AI與IoT攜手,精準農業未來已來

AIoT:農業領域的變革先鋒 在科技飛速發展的當下,人工智能(AI)與物聯網(IoT)的融合 ——AIoT,正逐漸成為推動各行業變革的關鍵力量,農業領域也不例外。AIoT 技術通過將 AI 的智能分析…

排錯-harbor-db容器異常重啟

排錯-harbor-db容器異常重啟 環境: docker 19.03 , harbor-db(postgresql) goharbor/harbor-db:v2.5.6 現象: harbor-db 容器一直restart,查看日志發現 報錯 initdb: error: directory "/var/lib/postgresql/data/pg13" exists…

Docker容器啟動失敗?無法啟動?

Docker容器無法啟動的疑難雜癥解析與解決方案 一、問題現象 Docker容器無法啟動是開發者在容器化部署中最常見的故障之一。盡管Docker提供了豐富的調試工具,但問題的根源往往隱藏在復雜的配置、環境依賴或資源限制中。本文將從環境變量配置錯誤這一細節問題入手&am…

查看購物車

一.查看購物車 查看購物車使用get請求。我們要查看當前用戶的購物車,就要獲取當前用戶的userId字段進行條件查詢。因為在用戶登錄時就已經將userId封裝在token中了,因此我們只需要解析token獲取userId即可,不需要前端再傳入參數了。 Control…

C/C++ 內存管理深度解析:從內存分布到實踐應用(malloc和new,free和delete的對比與使用,定位 new )

一、引言:理解內存管理的核心價值 在系統級編程領域,內存管理是決定程序性能、穩定性和安全性的關鍵因素。C/C 作為底層開發的主流語言,賦予開發者直接操作內存的能力,卻也要求開發者深入理解內存布局與生命周期管理。本文將從內…

使用Stable Diffusion(SD)中CFG參數指的是什么?該怎么用!

1.定義: CFG參數控制模型在生成圖像時,對提示詞(Prompt)的“服從程度”。 它衡量模型在“完全根據提示詞生成圖像”和“自由生成圖像”(不參考提示詞)之間的權衡程度。 數值范圍:常見范圍是 1 …

【GESP】C++三級練習 luogu-B2156 最長單詞 2

GESP三級練習,字符串練習(C三級大綱中6號知識點,字符串),難度★★☆☆☆。 題目題解詳見:https://www.coderli.com/gesp-3-luogu-b2156/ 【GESP】C三級練習 luogu-B2156 最長單詞 2 | OneCoderGESP三級練…

Linux網絡基礎 -- 局域網,廣域網,網絡協議,網絡傳輸的基本流程,端口號,網絡字節序

目錄 1. 計算機網絡背景 1.1 局域網 1.1.2 局域網的組成 1.2 廣域網 1.1.2 廣域網的組成 2. 初始網絡協議 2.1 網絡協議的定義和作用 2.2 網絡協議的分層結構 2.2.1 OSI七層模型 2.2.2 TCP/IP 五層(四層)模型 3. 再識網絡協議 3.1 為什么要有…

【PostgreSQL】超簡單的主從節點部署

1. 啟動數據庫 啟動主節點 docker run --name postgres-master -e POSTGRES_PASSWORDmysecretpassword -p 5432:5432 -d postgres啟動從節點 docker run --name postgres-slave -e POSTGRES_PASSWORDmysecretpassword -p 5432:5432 -d postgres需要配置掛載的存儲卷 2. 數據…

c#修改ComboBox當前選中項的文本

對于一個C#的Combobox列表,類型設置為下拉樣式,不允許輸入,只能選擇,樣子如下: 該控件的名字為 cbb1,如果要修改當前這個“A1區”的文件,則用如下方式: cbb1.Items[cbb1.SelectedInd…

Java設計模式之適配器模式:從入門到精通

適配器模式(Adapter Pattern)是Java中最常用的結構型設計模式之一,它像一座橋梁連接兩個不兼容的接口,使得原本由于接口不兼容而不能一起工作的類可以協同工作。本文將全面深入地解析適配器模式,從基礎概念到高級應用,包含豐富的代碼示例、詳細注釋、使用場景分析以及多維對…

中國黃土高原中部XF剖面磁化率和粒度數據

時間分辨率&#xff1a;1000年 < x空間分辨率為&#xff1a;空共享方式&#xff1a;申請獲取數據大小&#xff1b;35.75 KB數據時間范圍&#xff1a;743-0 ka元數據更新時間&#xff1a;2023-08-15 數據集摘要 該數據集包括中國黃土高原中部XF剖面磁化率和粒度數據。將所有…

【Python訓練營打卡】day23 @浙大疏錦行

test pipeline管道 知識回顧: 1. 轉化器和估計器的概念 2. 管道工程 3. ColumnTransformer和Pipeline類 作業&#xff1a; 整理下全部邏輯的先后順序&#xff0c;看看能不能制作出適合所有機器學習的通用pipeline 偽代碼 # 適合所有機器學習的通用pipeline #偽代碼 import p…

【android bluetooth 框架分析 02】【Module詳解 13】【CounterMetrics 模塊介紹】

1. CounterMetrics 介紹 CounterMetrics 模塊代碼很少&#xff0c; 我簡單介紹一下。 // system/gd/metrics/counter_metrics.cc #define LOG_TAG "BluetoothCounterMetrics"#include "metrics/counter_metrics.h"#include "common/bind.h" #i…

QMK鍵盤固件配置詳解

QMK鍵盤固件配置詳解 前言 大家好&#xff01;今天給大家帶來QMK鍵盤固件配置的詳細指南。如果你正在DIY機械鍵盤或者想要給自己的鍵盤刷固件&#xff0c;這篇文章絕對不容錯過。QMK是目前最流行的開源鍵盤固件框架之一&#xff0c;它允許我們對鍵盤進行高度自定義。接下來&a…

基于STM32、HAL庫的DPS368XTSA1氣壓傳感器 驅動程序設計

一、簡介: DPS368XTSA1 是 InvenSense(TDK 集團旗下公司)生產的一款高精度數字氣壓傳感器,專為需要精確測量氣壓和溫度的應用場景設計。它具有超低功耗、高精度、快速響應等特點,非常適合物聯網、可穿戴設備和無人機等應用。 二、硬件接口: DPS368XTSA1 引腳STM32L4XX 引…

因子分析——數學原理及R語言代碼

正交因子分析 目的數學原理參數估計方法主成分法主因子法極大似然法 因子旋轉模型檢驗因子得分加權最小二乘法回歸法 代碼實現注意事項例子 Reference 目的 FactorAnalysis的目的是從多個高度相關的觀測變量中提取出少數幾個LatentFactor&#xff0c;這些因子代表了變量背后的…

ACL訪問控制列表:access-list 10 permit 192.168.10.1

ACL訪問控制列表 標準ACL語法 1. 創建ACL access-list <編號> <動作> <源IP> <通配符掩碼> // 編號范圍 1-99 // 動作&#xff1a;permit 允許 、 deny 拒絕2. 示例 //允許192.168.1.0/24g整個網絡,0.0.0.255 反掩碼 access-list 10 permit 192.1…

解決社區錄音應用橫屏狀態下,錄音后無法播放的bug

最近看到社區有小伙伴反映&#xff0c;社區錄音應用橫屏時&#xff0c;錄音后無法播放的問題。現分享解決辦法。 社區錄音應用的來源&#xff1a;https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-5.0.2-Release/code/SystemFeature/Media/Recorder …

每周靶點分享:Angptl3、IgE、ADAM9及文獻分享:抗體的多樣性和特異性以及結構的新見解

本期精選了《脂質代謝的關鍵調控者Angptl3》《T細胞活化抑制因子VISTA靶點》《文獻分享&#xff1a;雙特異性抗體重輕鏈配對設計》三篇文章。以下為各研究內容的概述&#xff1a; 1. 脂質代謝的關鍵調控者Angptl3 血管生成素相關蛋白3&#xff08;Angptl3&#xff09;是血管生…