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