Java設(shè)計(jì)模式中的工廠及抽象工廠模式解析
前言
工廠模式作為創(chuàng)建型設(shè)計(jì)模式中常見的設(shè)計(jì)方法,一般情況下,工廠模式分為3種,簡單工作、工廠方法、抽象工作。
其實(shí)簡單工廠只是工廠方法的一種特例。
接下來,我們就來學(xué)習(xí)下如何實(shí)現(xiàn)工廠模式及幾種工廠模式之間的區(qū)分。
一、簡單工廠
我們?cè)谌粘i_發(fā)的過程中,一定遇到過,根據(jù)不同的類型去創(chuàng)建不同的類的業(yè)務(wù)場景
從而代碼中就會(huì)充斥著一段if,else的邏輯,對(duì)于有代碼潔癖的同事來說,可能看見過多的if,else就會(huì)比較煩,想著方法去去除掉。
這時(shí),簡單工廠的設(shè)計(jì)模式就派上用場了。
假設(shè)我們有如下場景,我們需要根據(jù)配置文件的后綴(json、xml、yaml、properties)選擇不同的解析器,將存儲(chǔ)在文件中的配置解析成內(nèi)存對(duì)象RuleConfig。
代碼如下:
public class RuleConfigSource { public RuleConfig load(String ruleConfigFilePath) { String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath); IRuleConfigParser parser = RuleConfigParserFactory.createParser(ruleConfigFileExtension); if (parser == null) { throw new InvalidRuleConfigException( "Rule config file format is not supported: " + ruleConfigFilePath); } String configText = ""; //從ruleConfigFilePath文件中讀取配置文本到configText中 RuleConfig ruleConfig = parser.parse(configText); return ruleConfig; } private String getFileExtension(String filePath) { //...解析文件名獲取擴(kuò)展名,比如rule.json,返回json return "json"; } } 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; } }
另外一種簡單工廠的實(shí)現(xiàn)方式如下:
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全憑你自己說了算 } IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase()); return parser; } }
對(duì)于上面兩種簡單工廠模式的實(shí)現(xiàn)方法,如果我們要添加新的 parser,那勢必要改動(dòng)到 RuleConfigParserFactory 的代碼,那這是不是違反開閉原則呢?
實(shí)際上,如果不是需要頻繁地添加新的 parser,只是偶爾修改一下 RuleConfigParserFactory 代碼,稍微不符合開閉原則,也是完全可以接受的。
總結(jié):盡管簡單工廠模式的代碼實(shí)現(xiàn)中,有多處 if 分支判斷邏輯,違背開閉原則,但權(quán)衡擴(kuò)展性和可讀性,這樣的代碼實(shí)現(xiàn)在大多數(shù)情況下(比如,不需要頻繁地添加 parser,也沒有太多的 parser)是沒有問題的。
二、工廠方法(Factory Method)
上面的提到的簡單工廠方法有一個(gè)比較大缺點(diǎn),那就是不符合開閉原則,當(dāng)新增一個(gè)parser的時(shí)候,我們需要更改Factory的代碼。
那我們有么有辦法可以解決這個(gè)問題勒。
那就是工廠方法的設(shè)計(jì)模式,他能利用多態(tài)的能力,對(duì)簡單工廠進(jìn)行改造。
代碼如下:
public interface IRuleConfigParserFactory { IRuleConfigParser createParser(); } public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory { @Override public IRuleConfigParser createParser() { return new JsonRuleConfigParser(); } } public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory { @Override public IRuleConfigParser createParser() { return new XmlRuleConfigParser(); } } public class YamlRuleConfigParserFactory implements IRuleConfigParserFactory { @Override public IRuleConfigParser createParser() { return new YamlRuleConfigParser(); } } public class PropertiesRuleConfigParserFactory implements IRuleConfigParserFactory { @Override public IRuleConfigParser createParser() { return new PropertiesRuleConfigParser(); } }
用工廠方法的方式實(shí)現(xiàn)上述代碼:
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 = ""; //從ruleConfigFilePath文件中讀取配置文本到configText中 RuleConfig ruleConfig = parser.parse(configText); return ruleConfig; } private String getFileExtension(String filePath) { //...解析文件名獲取擴(kuò)展名,比如rule.json,返回json return "json"; } }
從上面的代碼實(shí)現(xiàn)來看,工廠類對(duì)象的創(chuàng)建邏輯又耦合進(jìn)了 load() 函數(shù)中,跟我們最初的代碼版本非常相似,引入工廠方法非但沒有解決問題,反倒讓設(shè)計(jì)變得更加復(fù)雜了。那怎么來解決這個(gè)問題呢?
我們可以為工廠類再創(chuàng)建一個(gè)簡單工廠,也就是工廠的工廠,用來創(chuàng)建工廠類對(duì)象。這段話聽起來有點(diǎn)繞,我把代碼實(shí)現(xiàn)出來了,你一看就能明白了。其中,RuleConfigParserFactoryMap 類是創(chuàng)建工廠對(duì)象的工廠類,getParserFactory() 返回的是緩存好的單例工廠對(duì)象。
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) { //...解析文件名獲取擴(kuò)展名,比如rule.json,返回json return "json"; } } //因?yàn)楣S類只包含方法,不包含成員變量,完全可以復(fù)用, //不需要每次都創(chuàng)建新的工廠類對(duì)象,所以,簡單工廠模式的第二種實(shí)現(xiàn)思路更加合適。 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; } }
總結(jié):對(duì)于規(guī)則配置文件解析這個(gè)應(yīng)用場景來說,工廠模式需要額外創(chuàng)建諸多 Factory 類,也會(huì)增加代碼的復(fù)雜性,而且,每個(gè) Factory 類只是做簡單的 new 操作,功能非常單?。ㄖ挥幸恍写a),也沒必要設(shè)計(jì)成獨(dú)立的類,所以,在這個(gè)應(yīng)用場景下,簡單工廠模式簡單好用,比工廠方法模式更加合適。
三、如何選擇使用簡單工廠和工廠方式
當(dāng)對(duì)象的創(chuàng)建邏輯比較復(fù)雜,不只是簡單的 new 一下就可以,而是要組合其他類對(duì)象,做各種初始化操作的時(shí)候,我們推薦使用工廠方法模式,將復(fù)雜的創(chuàng)建邏輯拆分到多個(gè)工廠類中,讓每個(gè)工廠類都不至于過于復(fù)雜。而使用簡單工廠模式,將所有的創(chuàng)建邏輯都放到一個(gè)工廠類中,會(huì)導(dǎo)致這個(gè)工廠類變得很復(fù)雜。
除此之外,在某些場景下,如果對(duì)象不可復(fù)用,那工廠類每次都要返回不同的對(duì)象。如果我們使用簡單工廠模式來實(shí)現(xiàn),就只能選擇第一種包含 if 分支邏輯的實(shí)現(xiàn)方式。如果我們還想避免煩人的 if-else 分支邏輯,這個(gè)時(shí)候,我們就推薦使用工廠方法模式。
四、抽象工廠
在簡單工廠和工廠方法中,類只有一種分類方式。比如,在規(guī)則配置解析那個(gè)例子中,解析器類只會(huì)根據(jù)配置文件格式(Json、Xml、Yaml……)來分類。但是,如果類有兩種分類方式,比如,我們既可以按照配置文件格式來分類,也可以按照解析的對(duì)象(Rule 規(guī)則配置還是 System 系統(tǒng)配置)來分類,那就會(huì)對(duì)應(yīng)下面這 8 個(gè) parser 類。
針對(duì)規(guī)則配置的解析器:基于接口IRuleConfigParser
- JsonRuleConfigParser
- XmlRuleConfigParser
- YamlRuleConfigParser
- PropertiesRuleConfigParser
針對(duì)系統(tǒng)配置的解析器:基于接口ISystemConfigParser
- JsonSystemConfigParser
- XmlSystemConfigParser
- YamlSystemConfigParser
- PropertiesSystemConfigParser
針對(duì)這種特殊的場景,如果還是繼續(xù)用工廠方法來實(shí)現(xiàn)的話,我們要針對(duì)每個(gè) parser 都編寫一個(gè)工廠類,也就是要編寫 8 個(gè)工廠類。
如果我們未來還需要增加針對(duì)業(yè)務(wù)配置的解析器(比如 IBizConfigParser),那就要再對(duì)應(yīng)地增加 4 個(gè)工廠類。而我們知道,過多的類也會(huì)讓系統(tǒng)難維護(hù)。
這個(gè)問題該怎么解決呢?
抽象工廠就是針對(duì)這種非常特殊的場景而誕生的。
我們可以讓一個(gè)工廠負(fù)責(zé)創(chuàng)建多個(gè)不同類型的對(duì)象(IRuleConfigParser、ISystemConfigParser 等),而不是只創(chuàng)建一種 parser 對(duì)象。
這樣就可以有效地減少工廠類的個(gè)數(shù)。
具體的代碼實(shí)現(xiàn)如下所示:
public interface IConfigParserFactory { IRuleConfigParser createRuleParser(); ISystemConfigParser createSystemParser(); //此處可以擴(kuò)展新的parser類型,比如IBizConfigParser } public class JsonConfigParserFactory implements IConfigParserFactory { @Override public IRuleConfigParser createRuleParser() { return new JsonRuleConfigParser(); } @Override public ISystemConfigParser createSystemParser() { return new JsonSystemConfigParser(); } } public class XmlConfigParserFactory implements IConfigParserFactory { @Override public IRuleConfigParser createRuleParser() { return new XmlRuleConfigParser(); } @Override public ISystemConfigParser createSystemParser() { return new XmlSystemConfigParser(); } } // 省略YamlConfigParserFactory和PropertiesConfigParserFactory代碼
總結(jié)
當(dāng)創(chuàng)建邏輯比較復(fù)雜,是一個(gè)“大工程”的時(shí)候,我們就考慮使用工廠模式,封裝對(duì)象的創(chuàng)建過程,將對(duì)象的創(chuàng)建和使用相分離。
何為創(chuàng)建邏輯比較復(fù)雜呢?
- 第一種情況:類似規(guī)則配置解析的例子,代碼中存在 if-else 分支判斷,動(dòng)態(tài)地根據(jù)不同的類型創(chuàng)建不同的對(duì)象。針對(duì)這種情況,我們就考慮使用工廠模式,將這一大坨 if-else 創(chuàng)建對(duì)象的代碼抽離出來,放到工廠類中。
- 還有一種情況,盡管我們不需要根據(jù)不同的類型創(chuàng)建不同的對(duì)象,但是,單個(gè)對(duì)象本身的創(chuàng)建過程比較復(fù)雜,比如前面提到的要組合其他類對(duì)象,做各種初始化操作。在這種情況下,我們也可以考慮使用工廠模式,將對(duì)象的創(chuàng)建過程封裝到工廠類中。
現(xiàn)在,我們上升一個(gè)思維層面來看工廠模式,它的作用無外乎下面這四個(gè)。這也是判斷要不要使用工廠模式的最本質(zhì)的參考標(biāo)準(zhǔn)。
- 封裝變化:創(chuàng)建邏輯有可能變化,封裝成工廠類之后,創(chuàng)建邏輯的變更對(duì)調(diào)用者透明。
- 代碼復(fù)用:創(chuàng)建代碼抽離到獨(dú)立的工廠類之后可以復(fù)用。
- 隔離復(fù)雜性:封裝復(fù)雜的創(chuàng)建邏輯,調(diào)用者無需了解如何創(chuàng)建對(duì)象。
- 控制復(fù)雜度:將創(chuàng)建代碼抽離出來,讓原本的函數(shù)或類職責(zé)更單一,代碼更簡潔
到此這篇關(guān)于Java設(shè)計(jì)模式中的工廠及抽象工廠模式解析的文章就介紹到這了,更多相關(guān)Java工廠及抽象工廠模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot2.6.x的啟動(dòng)流程與自動(dòng)配置詳解
這篇文章主要給大家介紹了關(guān)于Springboot2.6.x的啟動(dòng)流程與自動(dòng)配置的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-01-01Java中Controller、Service、Dao/Mapper層的區(qū)別與用法
在Java開發(fā)中,通常會(huì)采用三層架構(gòu)(或稱MVC架構(gòu))來劃分程序的職責(zé)和功能,分別是Controller層、Service層、Dao/Mapper層,本文將詳細(xì)給大家介紹了三層的區(qū)別和用法,需要的朋友可以參考下2023-05-05基于Java SSM框架開發(fā)圖書借閱系統(tǒng)源代碼
本文給大家介紹了基于Java SSM框架開發(fā)圖書借閱系統(tǒng),開發(fā)環(huán)境基于idea2020+mysql數(shù)據(jù)庫,前端框架使用bootstrap4框架,完美了實(shí)現(xiàn)圖書借閱系統(tǒng),喜歡的朋友快來體驗(yàn)吧2021-05-05IDEA配置Gradle及Gradle安裝的實(shí)現(xiàn)步驟
本文主要介紹了IDEA配置Gradle及Gradle安裝的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-08-08SpringBoot操作spark處理hdfs文件的操作方法
本文介紹了如何使用Spring Boot操作Spark處理HDFS文件,包括導(dǎo)入依賴、配置Spark信息、編寫Controller和Service處理地鐵數(shù)據(jù)、運(yùn)行項(xiàng)目以及觀察Spark和HDFS的狀態(tài),感興趣的朋友跟隨小編一起看看吧2025-01-01Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(21)
下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧,希望可以幫到你2021-07-07