文章目錄
- 一. 簡單工廠(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
參考:
王爭:《設計模式之美》