Java設(shè)計(jì)模式中的工廠及抽象工廠模式解析
前言
工廠模式作為創(chuàng)建型設(shè)計(jì)模式中常見的設(shè)計(jì)方法,一般情況下,工廠模式分為3種,簡單工作、工廠方法、抽象工作。
其實(shí)簡單工廠只是工廠方法的一種特例。
接下來,我們就來學(xué)習(xí)下如何實(shí)現(xiàn)工廠模式及幾種工廠模式之間的區(qū)分。
一、簡單工廠
我們在日常開發(fā)的過程中,一定遇到過,根據(jù)不同的類型去創(chuàng)建不同的類的業(yè)務(wù)場景
從而代碼中就會充斥著一段if,else的邏輯,對于有代碼潔癖的同事來說,可能看見過多的if,else就會比較煩,想著方法去去除掉。
這時,簡單工廠的設(shè)計(jì)模式就派上用場了。
假設(shè)我們有如下場景,我們需要根據(jù)配置文件的后綴(json、xml、yaml、properties)選擇不同的解析器,將存儲在文件中的配置解析成內(nèi)存對象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; } }
對于上面兩種簡單工廠模式的實(shí)現(xiàn)方法,如果我們要添加新的 parser,那勢必要改動到 RuleConfigParserFactory 的代碼,那這是不是違反開閉原則呢?
實(shí)際上,如果不是需要頻繁地添加新的 parser,只是偶爾修改一下 RuleConfigParserFactory 代碼,稍微不符合開閉原則,也是完全可以接受的。
總結(jié):盡管簡單工廠模式的代碼實(shí)現(xiàn)中,有多處 if 分支判斷邏輯,違背開閉原則,但權(quán)衡擴(kuò)展性和可讀性,這樣的代碼實(shí)現(xiàn)在大多數(shù)情況下(比如,不需要頻繁地添加 parser,也沒有太多的 parser)是沒有問題的。
二、工廠方法(Factory Method)
上面的提到的簡單工廠方法有一個比較大缺點(diǎn),那就是不符合開閉原則,當(dāng)新增一個parser的時候,我們需要更改Factory的代碼。
那我們有么有辦法可以解決這個問題勒。
那就是工廠方法的設(shè)計(jì)模式,他能利用多態(tài)的能力,對簡單工廠進(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)來看,工廠類對象的創(chuàng)建邏輯又耦合進(jìn)了 load() 函數(shù)中,跟我們最初的代碼版本非常相似,引入工廠方法非但沒有解決問題,反倒讓設(shè)計(jì)變得更加復(fù)雜了。那怎么來解決這個問題呢?
我們可以為工廠類再創(chuàng)建一個簡單工廠,也就是工廠的工廠,用來創(chuàng)建工廠類對象。這段話聽起來有點(diǎn)繞,我把代碼實(shí)現(xiàn)出來了,你一看就能明白了。其中,RuleConfigParserFactoryMap 類是創(chuàng)建工廠對象的工廠類,getParserFactory() 返回的是緩存好的單例工廠對象。
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)建新的工廠類對象,所以,簡單工廠模式的第二種實(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é):對于規(guī)則配置文件解析這個應(yīng)用場景來說,工廠模式需要額外創(chuàng)建諸多 Factory 類,也會增加代碼的復(fù)雜性,而且,每個 Factory 類只是做簡單的 new 操作,功能非常單?。ㄖ挥幸恍写a),也沒必要設(shè)計(jì)成獨(dú)立的類,所以,在這個應(yīng)用場景下,簡單工廠模式簡單好用,比工廠方法模式更加合適。
三、如何選擇使用簡單工廠和工廠方式
當(dāng)對象的創(chuàng)建邏輯比較復(fù)雜,不只是簡單的 new 一下就可以,而是要組合其他類對象,做各種初始化操作的時候,我們推薦使用工廠方法模式,將復(fù)雜的創(chuàng)建邏輯拆分到多個工廠類中,讓每個工廠類都不至于過于復(fù)雜。而使用簡單工廠模式,將所有的創(chuàng)建邏輯都放到一個工廠類中,會導(dǎo)致這個工廠類變得很復(fù)雜。
除此之外,在某些場景下,如果對象不可復(fù)用,那工廠類每次都要返回不同的對象。如果我們使用簡單工廠模式來實(shí)現(xiàn),就只能選擇第一種包含 if 分支邏輯的實(shí)現(xiàn)方式。如果我們還想避免煩人的 if-else 分支邏輯,這個時候,我們就推薦使用工廠方法模式。
四、抽象工廠
在簡單工廠和工廠方法中,類只有一種分類方式。比如,在規(guī)則配置解析那個例子中,解析器類只會根據(jù)配置文件格式(Json、Xml、Yaml……)來分類。但是,如果類有兩種分類方式,比如,我們既可以按照配置文件格式來分類,也可以按照解析的對象(Rule 規(guī)則配置還是 System 系統(tǒng)配置)來分類,那就會對應(yīng)下面這 8 個 parser 類。
針對規(guī)則配置的解析器:基于接口IRuleConfigParser
- JsonRuleConfigParser
- XmlRuleConfigParser
- YamlRuleConfigParser
- PropertiesRuleConfigParser
針對系統(tǒng)配置的解析器:基于接口ISystemConfigParser
- JsonSystemConfigParser
- XmlSystemConfigParser
- YamlSystemConfigParser
- PropertiesSystemConfigParser
針對這種特殊的場景,如果還是繼續(xù)用工廠方法來實(shí)現(xiàn)的話,我們要針對每個 parser 都編寫一個工廠類,也就是要編寫 8 個工廠類。
如果我們未來還需要增加針對業(yè)務(wù)配置的解析器(比如 IBizConfigParser),那就要再對應(yīng)地增加 4 個工廠類。而我們知道,過多的類也會讓系統(tǒng)難維護(hù)。
這個問題該怎么解決呢?
抽象工廠就是針對這種非常特殊的場景而誕生的。
我們可以讓一個工廠負(fù)責(zé)創(chuàng)建多個不同類型的對象(IRuleConfigParser、ISystemConfigParser 等),而不是只創(chuàng)建一種 parser 對象。
這樣就可以有效地減少工廠類的個數(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ù)雜,是一個“大工程”的時候,我們就考慮使用工廠模式,封裝對象的創(chuàng)建過程,將對象的創(chuàng)建和使用相分離。
何為創(chuàng)建邏輯比較復(fù)雜呢?
- 第一種情況:類似規(guī)則配置解析的例子,代碼中存在 if-else 分支判斷,動態(tài)地根據(jù)不同的類型創(chuàng)建不同的對象。針對這種情況,我們就考慮使用工廠模式,將這一大坨 if-else 創(chuàng)建對象的代碼抽離出來,放到工廠類中。
- 還有一種情況,盡管我們不需要根據(jù)不同的類型創(chuàng)建不同的對象,但是,單個對象本身的創(chuàng)建過程比較復(fù)雜,比如前面提到的要組合其他類對象,做各種初始化操作。在這種情況下,我們也可以考慮使用工廠模式,將對象的創(chuàng)建過程封裝到工廠類中。
現(xiàn)在,我們上升一個思維層面來看工廠模式,它的作用無外乎下面這四個。這也是判斷要不要使用工廠模式的最本質(zhì)的參考標(biāo)準(zhǔn)。
- 封裝變化:創(chuàng)建邏輯有可能變化,封裝成工廠類之后,創(chuàng)建邏輯的變更對調(diào)用者透明。
- 代碼復(fù)用:創(chuàng)建代碼抽離到獨(dú)立的工廠類之后可以復(fù)用。
- 隔離復(fù)雜性:封裝復(fù)雜的創(chuàng)建邏輯,調(diào)用者無需了解如何創(chuàng)建對象。
- 控制復(fù)雜度:將創(chuàng)建代碼抽離出來,讓原本的函數(shù)或類職責(zé)更單一,代碼更簡潔
到此這篇關(guān)于Java設(shè)計(jì)模式中的工廠及抽象工廠模式解析的文章就介紹到這了,更多相關(guān)Java工廠及抽象工廠模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中Controller、Service、Dao/Mapper層的區(qū)別與用法
在Java開發(fā)中,通常會采用三層架構(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ì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(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í)題(分享)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望可以幫到你2021-07-07