【設計模式之美】【建造型】工廠模式:通過面向接口編程思路,串起業務流程

文章目錄

  • 一. 簡單工廠(Simple Factory)
    • 第一種簡單工廠:面向接口編程與工廠類:劃分功能職責
    • 第二種:單例+簡單工廠:節省內存和對象創建的時間
  • 二. 工廠方法(Factory Method):進一步抽象:通過面向接口的思路創建對象
  • 三. 什么時候使用工廠方法
  • 四. 抽象工廠

一般情況下,工廠模式分為三種更加細分的類型:簡單工廠、工廠方法和抽象工廠。

什么時候該用工廠模式?相對于直接 new 來創建對象,用工廠模式來創建究竟有什么好處呢?這是本文將要討論的事情。

?

一. 簡單工廠(Simple Factory)

第一種簡單工廠:面向接口編程與工廠類:劃分功能職責

如下代碼流程描述了:根據不同的文件后綴(面向接口編程)創建不同的對象,然后執行相同方法的不同邏輯。

public class RuleConfigSource {public RuleConfig load(String ruleConfigFilePath) {//1. 獲取文件后綴String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);//這個是函數式接口,有一個抽象方法parse。//2. 根據不同的文件后綴,創建不同的parse對象(面向接口編程)IRuleConfigParser parser = null;if ("json".equalsIgnoreCase(ruleConfigFileExtension)) {parser = new JsonRuleConfigParser();} else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)) {parser = new XmlRuleConfigParser();} else if ("yaml".equalsIgnoreCase(ruleConfigFileExtension)) {parser = new YamlRuleConfigParser();} else if ("properties".equalsIgnoreCase(ruleConfigFileExtension)) {parser = new PropertiesRuleConfigParser();} else {throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);}//3. 根據具體實現解析文本String configText = "";//從ruleConfigFilePath文件中讀取配置文本到configText中RuleConfig ruleConfig = parser.parse(configText);return ruleConfig;}private String getFileExtension(String filePath) {//...解析文件名獲取擴展名,比如rule.json,返回jsonreturn "json";}
}

?

為了增加代碼的可讀性:將功能獨立的代碼封裝成函數:將創建對象的邏輯抽取出來

  public RuleConfig load(String ruleConfigFilePath) {String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);//1. 創建對象IRuleConfigParser parser = createParser(ruleConfigFileExtension);//check parser=?nullString configText = "";//從ruleConfigFilePath文件中讀取配置文本到configText中//2. 執行指定對象的parse方法(面向接口編程)RuleConfig ruleConfig = parser.parse(configText);return ruleConfig;}private String getFileExtension(String filePath) {//...解析文件名獲取擴展名,比如rule.json,返回jsonreturn "json";}private IRuleConfigParser createParser(String configFormat) {IRuleConfigParser parser = null;if ("json".equalsIgnoreCase(configFormat)) {parser = new JsonRuleConfigParser();} else if ("xml".equalsIgnoreCase(configFormat)) {parser = new XmlRuleConfigParser();} else if ("yaml".equalsIgnoreCase(configFormat)) {parser = new YamlRuleConfigParser();} else if ("properties".equalsIgnoreCase(configFormat)) {parser = new PropertiesRuleConfigParser();}return parser;}
}

?

類責任單一:對象創建的方法放到獨立一個類中,具體地,
將 createParser() 函數剝離到一個獨立的類中,讓這個類只負責對象的創建。

...public class RuleConfigParserFactory {public static IRuleConfigParser createParser(String configFormat) {IRuleConfigParser parser = null;if ("json".equalsIgnoreCase(configFormat)) {parser = new JsonRuleConfigParser();} else if ("xml".equalsIgnoreCase(configFormat)) {parser = new XmlRuleConfigParser();} else if ("yaml".equalsIgnoreCase(configFormat)) {parser = new YamlRuleConfigParser();} else if ("properties".equalsIgnoreCase(configFormat)) {parser = new PropertiesRuleConfigParser();}return parser;}
}

類名與方法名:

  • 大部分工廠類都是以“Factory”這個單詞結尾的,但也不是必須的,比如 Java 中的 DateFormat、Calender。
  • 工廠類中創建對象的方法一般都是 create 開頭,比如代碼中的 createParser(),但有的也命名為 getInstance()、createInstance()、newInstance(),有的甚至命名為 valueOf()(比如 Java String 類的 valueOf() 函數)等等,這個我們根據具體的場景和習慣來命名就好。

?

面向接口編程


面向接口編程(Interface-Oriented Programming)其核心思想是依賴于接口(或抽象類)來編寫程序,而不是依賴于具體的實現類。具體地,通過接口來串聯起業務代碼的邏輯,而不是具體的實現類。

?

面向接口有如下好處:

  • 松耦合:各個模塊之間的依賴關系更松散。這使得系統中的組件可以更獨立地開發、測試和部署
  • 可擴展性:新的功能可以通過實現現有接口來擴展系統,而不會影響到已有的代碼。
  • 增強代碼復用:通過面向接口編程,可以提高代碼的可重用性。不同的實現類可以實現相同的接口,使得同一份代碼可以適應不同的實際需求。
  • 測試和調試:面向接口編程使得單元測試更加容易,可以使用模擬對象(mock objects)來模擬接口的行為,從而進行更有效的單元測試和調試。

?

第二種:單例+簡單工廠:節省內存和對象創建的時間

為了節省內存和對象創建的時間,我們可以將 parser 事先創建好緩存起來。當調用 createParser() 函數的時候,我們從緩存中取出 parser 對象直接使用。

public class RuleConfigParserFactory {private static final Map<String, RuleConfigParser> cachedParsers = new HashMap<>();static {cachedParsers.put("json", new JsonRuleConfigParser());cachedParsers.put("xml", new XmlRuleConfigParser());cachedParsers.put("yaml", new YamlRuleConfigParser());cachedParsers.put("properties", new PropertiesRuleConfigParser());}public static IRuleConfigParser createParser(String configFormat) {if (configFormat == null || configFormat.isEmpty()) {return null;//返回null還是IllegalArgumentException全憑你自己說了算}//也去掉了if elseIRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase());return parser;}
}

對于上面兩種簡單工廠模式的實現方法,如果我們要添加新的 parser,那勢必要改動到 RuleConfigParserFactory 的代碼,那這是不是違反開閉原則呢?實際上,如果不是需要頻繁地添加新的 parser,只是偶爾修改一下 RuleConfigParserFactory 代碼,稍微不符合開閉原則,也是完全可以接受的。

?

總結一下,盡管簡單工廠模式的代碼實現中,有多處 if 分支判斷邏輯,違背開閉原則,但權衡擴展性和可讀性,這樣的代碼實現在大多數情況下(比如,不需要頻繁地添加 parser,也沒有太多的 parser)是沒有問題的。

?

二. 工廠方法(Factory Method):進一步抽象:通過面向接口的思路創建對象

如果我們非得要將 if 分支邏輯去掉,那該怎么辦呢?比較經典處理方法就是利用多態。按照多態的實現思路,對上面的代碼進行重構。

如下我們新增一個創建對象的工廠,這樣當我們新增一種 parser 的時候,只需要新增一個實現了 IRuleConfigParserFactory 接口的 Factory 類即可。所以,工廠方法模式比起簡單工廠模式更加符合開閉原則

public interface IRuleConfigParserFactory {IRuleConfigParser createParser();
}public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory {@Overridepublic IRuleConfigParser createParser() {return new JsonRuleConfigParser();}
}XmlRuleConfigParserFactory
YamlRuleConfigParserFactory
...-------------public class RuleConfigSource {public RuleConfig load(String ruleConfigFilePath) {String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);IRuleConfigParserFactory parserFactory = null;if ("json".equalsIgnoreCase(ruleConfigFileExtension)) {parserFactory = new JsonRuleConfigParserFactory();} else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)) {parserFactory = new XmlRuleConfigParserFactory();} else if ("yaml".equalsIgnoreCase(ruleConfigFileExtension)) {parserFactory = new YamlRuleConfigParserFactory();} else if ("properties".equalsIgnoreCase(ruleConfigFileExtension)) {parserFactory = new PropertiesRuleConfigParserFactory();} else {throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);}// 這里是工廠方法的關鍵思路點:通過面向接口的思路來創建對象IRuleConfigParser parser = parserFactory.createParser();String configText = "";RuleConfig ruleConfig = parser.parse(configText);return ruleConfig;}}

但注意代碼的復雜度此時增加了

工廠類對象的創建邏輯又耦合進了 load() 函數中,跟我們最初的代碼版本非常相似,引入工廠方法非但沒有解決問題,反倒讓設計變得更加復雜了。

?

我們為工廠類再創建一個簡單工廠,也就是工廠的工廠,用來創建工廠類對象。


public class RuleConfigSource {public RuleConfig load(String ruleConfigFilePath) {String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);IRuleConfigParserFactory parserFactory = RuleConfigParserFactoryMap.getParserFactory(ruleConfigFileExtension);if (parserFactory == null) {throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);}IRuleConfigParser parser = parserFactory.createParser();String configText = "";//從ruleConfigFilePath文件中讀取配置文本到configText中RuleConfig ruleConfig = parser.parse(configText);return ruleConfig;}private String getFileExtension(String filePath) {//...解析文件名獲取擴展名,比如rule.json,返回jsonreturn "json";}
}//因為工廠類只包含方法,不包含成員變量,完全可以復用,
//不需要每次都創建新的工廠類對象,所以,簡單工廠模式的第二種實現思路更加合適。
public class RuleConfigParserFactoryMap { //工廠的工廠private static final Map<String, IRuleConfigParserFactory> cachedFactories = new HashMap<>();static {cachedFactories.put("json", new JsonRuleConfigParserFactory());cachedFactories.put("xml", new XmlRuleConfigParserFactory());cachedFactories.put("yaml", new YamlRuleConfigParserFactory());cachedFactories.put("properties", new PropertiesRuleConfigParserFactory());}public static IRuleConfigParserFactory getParserFactory(String type) {if (type == null || type.isEmpty()) {return null;}IRuleConfigParserFactory parserFactory = cachedFactories.get(type.toLowerCase());return parserFactory;}
}

當我們需要添加新的規則配置解析器的時候,我們只需要創建新的 parser 類和 parser factory 類,并且在 RuleConfigParserFactoryMap 類中,將新的 parser factory 對象添加到 cachedFactories 中即可。代碼的改動非常少,基本上符合開閉原則。

?

三. 什么時候使用工廠方法

實際上,對于規則配置文件解析這個應用場景來說,工廠模式需要額外創建諸多 Factory 類,也會增加代碼的復雜性,而且,每個 Factory 類只是做簡單的 new 操作,功能非常單薄(只有一行代碼),也沒必要設計成獨立的類,所以,在這個應用場景下,簡單工廠模式簡單好用,比工廠方法模式更加合適。

?

那什么時候該用工廠方法模式,而非簡單工廠模式呢?

  • 如果代碼塊本身并不復雜,就幾行代碼而已,我們完全沒必要將它拆分成單獨的函數或者類。
  • 當對象的創建邏輯比較復雜,不只是簡單的 new 一下就可以,而是要組合其他類對象,做各種初始化操作的時候,我們推薦使用工廠方法模式,將復雜的創建邏輯拆分到多個工廠類中,讓每個工廠類都不至于過于復雜。比如:不同jdbc數據源的管理、flink connector的管理。

?

四. 抽象工廠

抽象工廠模式的應用場景比較特殊,沒有前兩種常用。

在簡單工廠和工廠方法中,類只有一種分類方式。當有多個類的分類時,我們可以讓一個工廠負責創建多個不同類型的對象,而不是只創建一種 parser 對象。如下:

public interface IConfigParserFactory {IRuleConfigParser createRuleParser();ISystemConfigParser createSystemParser();//此處可以擴展新的parser類型,比如IBizConfigParser
}public class JsonConfigParserFactory implements IConfigParserFactory {@Overridepublic IRuleConfigParser createRuleParser() {return new JsonRuleConfigParser();}@Overridepublic ISystemConfigParser createSystemParser() {return new JsonSystemConfigParser();}
}... 
XmlConfigParserFactory 
YamlConfigParserFactory
PropertiesConfigParserFactory

參考:
王爭:《設計模式之美》

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

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

相關文章

文心一言:如何備考軟考架構師

提問&#xff1a;軟考高級:架構師指導&#xff0c;有哪些考點、重點考點有哪些&#xff1f;需要如何備考&#xff1f;案例分析題如何做&#xff1f;論文如何寫&#xff1f; 文心一言回答1700字如下&#xff1a; 針對軟考高級架構師的考試&#xff0c;以下是對考點、重點考點、備…

DiffusionModel-Transformer知識回顧

論文鏈接&#xff1a; Attention Is All You Need CNN vs RNN vs Transformer CNN&#xff08;卷積神經網絡&#xff09; 特點&#xff1a; 局部連接&#xff1a;每個卷積神經元只與輸入數據的局部區域連接&#xff0c;這有助于捕捉局部特征。權重共享&#xff1a;卷積核的權重…

Android筆記:Constructor (xxx) has been changed after generation.

遇到此報錯時&#xff0c;onstructor (xxx) has been changed after generation.是因為修改了實體類字段后什么都不修改的話就會報這個錯 這條信息是關于代碼生成和代碼變更的警告。當你使用某些工具&#xff08;如注解處理器、代碼生成庫等&#xff09;來自動生成代碼時&…

運行在Linux上的程序越來越慢的排查思路

1、通過free -h 排查內存使用情況&#xff0c;是否內存滿了 2、通過df -h 排查磁盤的使用情況&#xff0c;磁盤是否沒有空間了 3、檢查系統資源配置情況&#xff0c;比如使用ulimit -a檢查當前會話的資源限制&#xff0c;如最大文件數、打開文件描述符數等&#xff0c;看是否…

清華計算幾何-ConvexHull(凸包)-求極點InTriangle/ToLeft Test

ConvexHull(凸包) 凸包是什么 凸包是計算幾何一個非常基礎核心的概念。我理解的凸包就是給定一個點集合, 最外圍的點的包圍體就是凸包。如下所示: 極點(ExtremityPoint) 給定的點集合中, 如果一個點存在一條直線, 讓其他所有點都在于該直線的同一側, 則該點為極點。 非極點 …

如何理解electron 的預加載腳本

在 Electron 應用中,預加載腳本(Preload Script)是一個非常重要的概念,它允許你在渲染進程(web 頁面)和主進程之間創建一個安全的橋梁。預加載腳本運行在 Node.js 環境中,但位于渲染進程的一個單獨的上下文中,這意味著它可以訪問 Node.js 的 API,但無法直接訪問 DOM。…

JavaScript進階(7) ----構造函數和原型對象

目錄 構造函數 prototype 定義&#xff1a; 使用場景&#xff1a; constructor 使用場景&#xff1a; 原型proto 原型鏈 定義 特點 instanceof 運算符 原型繼承的基本概念 在JavaScript中&#xff0c;構造函數和原型是面向對象編程的核心概念&#xff0c;它們共同構…

海康工業相機驅動

1.新建基于對話框的MFC程序&#xff0c;界面布局如下 2.修改控件ID&#xff0c;為控件綁定變量 3.創建全局變量&#xff0c;構造函數中初始化變量&#xff0c;初始化對話框界面&#xff0c;補齊各控件按鈕響應函數 全文程序如下&#xff1a; // MFC_GrabimageDlg.h : 頭文件 /…

【動態規劃Ⅰ】斐波那契、爬樓梯、楊輝三角

動態規劃—斐波那契系列 什么是動態規劃斐波那契數組相關題目509. 斐波那契數 Easy1137. 第 N 個泰波那契數 Easy 楊輝三角118. 楊輝三角 Easy 爬樓梯相關題目70. 爬樓梯 Easy746. 使用最小花費爬樓梯 Easy 什么是動態規劃 動態規劃是一種通過將原問題分解為相對簡單的子問題來…

linux下解壓命令

在Linux下&#xff0c;解壓縮文件通常涉及多種命令&#xff0c;具體取決于文件的壓縮格式。以下是一些常用的解壓縮命令&#xff1a; tar.gz / .tgz 如果文件擴展名為 .tar.gz 或 .tgz&#xff0c;你可以使用 tar 命令來解壓縮&#xff1a; tar -xzf filename.tar.gz這里的 -x …

近期幾首小詩匯總-生活~卷

生活 為生活飄零&#xff0c;風雨都不阻 路見盲人艱&#xff0c;為她心點燈 賀中科大家長論壇成立十五周年 科學家園有喜賀 園外丑漢翹望中 曾一學子入我科 正育科二盼長大 憧憬也能入此家 與科學家論短長 園外翹首聽高論 發現有隙入此壇 竟然也能注冊成 入園瀏覽惶然立 此貼…

系統架構的基礎:定義、原則與發展歷程

目錄 1. 系統架構的定義 2. 系統架構的基本組成部分 2.1 架構層次 2.2 架構視圖 2.3 架構原則 3. 系統架構的發展歷程 3.1 初期階段:單體架構(Monolithic Architecture) 3.2 面向對象和組件化階段 3.3 客戶端-服務器架構(Client-Server Architecture) 3.4 三層架…

在 Linux 上使用 lspci 命令查看 PCI 總線硬件設備信息

lspci 命令用于顯示 Linux 系統上的設備和驅動程序 當在個人電腦或服務器上運行 Linux 時&#xff0c;有時需要識別該系統中的硬件。lspci 命令用于顯示連接到 PCI 總線的所有設備&#xff0c;從而滿足上述需求。該命令由 pciutils 包提供&#xff0c;可用于各種基于 Linux 和…

JAVA中的回溯算法解空間樹,八皇后問題以及騎士游歷問題超詳解

1.回溯算法的概念 回溯算法顧名思義就是有回溯的算法 回溯算法實際上一個類似枚舉的搜索嘗試過程&#xff0c;主要是在搜索嘗試過程中尋找問題的解&#xff0c;當發現已不滿足求解條件時&#xff0c;就“回溯”返回&#xff0c;嘗試別的路徑。回溯法是一種選優搜索法&#xff…

E12.【C語言】練習:求兩個數的最大公約數

1.枚舉 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() {int a 0;int b 0;int tmp 0;scanf("%d %d", &a, &b);if (a < b){for (int i1; i < a; i){if (0a% i && 0b%i)tmp i;}}if (a>b){for (int i 1; i <…

[線性RNN系列] Mamba: S4史詩級升級

前言 iclr24終于可以在openreview上看預印本了 這篇&#xff08;可能是顛覆之作&#xff09;文風一眼c re組出品&#xff1b;效果實在太驚艷了&#xff0c;實驗相當完善&#xff0c;忍不住寫一篇解讀分享分享。 TL;DR &#xff08;overview&#xff09; Structured State-Sp…

Nginx 日志統計分析命令

統計訪問量最多的IP地址&#xff1a; awk {print $1} /path/to/nginx/access.log | sort | uniq -c | sort -nr | head -n 10統計不同狀態碼的出現次數&#xff1a; awk {print $9} /path/to/nginx/access.log | sort | uniq -c | sort -nr統計訪問量最多的URL&#xff1a; awk…

SQL Server端口配置指南

SQL Server是微軟推出的關系型數據庫管理系統&#xff0c;它支持多種操作系統平臺。默認情況下&#xff0c;SQL Server使用TCP/IP協議的1433端口進行通信。然而&#xff0c;出于安全或其他考慮&#xff0c;我們可能需要更改SQL Server實例的默認端口。本文將指導你如何更改SQL …

利率債與信用債的區別及其與債券型基金的關系

利率債與信用債的定義及其區別 定義 利率債&#xff1a; 定義&#xff1a;利率債是指由主權或類主權主體&#xff08;如中華人民共和國財政部、國家開發銀行等&#xff09;發行的債券。這些債券通常被認為沒有信用風險&#xff0c;因為它們由國家信用背書。特點&#xff1a;由…

【Python】 深入了解 Python 字典的 | 更新操作

我白天是個 搞笑廢物 表演不在乎 夜晚變成 憂傷怪物 撕扯著孤獨 我曾經是個 感性動物 小心地感觸 現在變成 無關人物 &#x1f3b5; 張碧晨/王赫野《何物》 Python 3.9 引入了一種新的字典更新操作&#xff0c;即使用 | 運算符合并字典。這種方式不僅簡潔…