簡單工廠模式(靜態(tài)工廠)
舉例:我們根據(jù)配置文件的后綴(json星虹、xml零抬、yaml、properties)宽涌,選擇不同的解析器(JsonRuleConfigParser平夜、XmlRuleConfigParser……),將存儲在文件中的配置解析成內(nèi)存對象 RuleConfig卸亮。
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;
}
}
以上一種實現(xiàn)方法叫作簡單工廠模式的第一種實現(xiàn)方法忽妒,把下面這種實現(xiàn)方法叫作簡單工廠模式的第二種實現(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;
}
}
對于上面兩種簡單工廠模式的實現(xiàn)方法,如果我們要添加新的 parser锰扶,那勢必要改動到
RuleConfigParserFactory 的代碼,那這是不是違反開閉原則呢献酗?實際上,如果不是需要頻繁地添加新的 parser坷牛,只是偶爾修改一下 RuleConfigParserFactory 代碼罕偎,稍微不符合開閉原則也是完全可以接受的。
工廠方法模式
如果我們非得要將 if 分支邏輯去掉京闰,那該怎么辦呢颜及?比較經(jīng)典處理方法就是利用多態(tài)。按
照多態(tài)的實現(xiàn)思路蹂楣,對上面的代碼進行重構(gòu)俏站。重構(gòu)之后的代碼如下所示:
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 IRuleConfigParserFact
@Override
public IRuleConfigParser createParser() {
return new PropertiesRuleConfigParser();
實際上,這就是工廠方法模式的典型代碼實現(xiàn)痊土。這樣當我們新增一種 parser 的時候肄扎,只需要新增一個實現(xiàn)了 IRuleConfigParserFactory 接口的 Factory 類即可。所以赁酝,工廠方法模式比起簡單工廠模式更加符合開閉原則犯祠。
從上面的工廠方法的實現(xiàn)來看,一切都很完美酌呆,但是實際上存在挺大的問題衡载。問題存在于這些工廠類的使用上。接下來隙袁,我們看一下痰娱,如何用這些工廠類來實現(xiàn) RuleConfigSource的 load() 函數(shù)。具體的代碼如下所示:
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 support");
}
IRuleConfigParser parser = parserFactory.createParser();
String configText = "";
//從ruleConfigFilePath文件中讀取配置文本到configText中
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(String filePath) {
//...解析文件名獲取擴展名菩收,比如rule.json梨睁,返回json
return "json";
}
}
從上面的代碼實現(xiàn)來看,工廠類對象的創(chuàng)建邏輯又耦合進了 load() 函數(shù)中坛梁,跟我們最初的代碼版本非常相似而姐,引入工廠方法非但沒有解決問題,反倒讓設(shè)計變得更加復(fù)雜了划咐。那怎么來解決這個問題呢?
我們可以為工廠類再創(chuàng)建一個簡單工廠钧萍,也就是工廠的工廠褐缠,用來創(chuàng)建工廠類對象。
RuleConfigParserFactoryMap 類是創(chuàng)建工廠對象的工廠類风瘦,getParserFactory() 返回的是緩存好的單例工廠對象队魏。
當我們需要添加新的規(guī)則配置解析器的時候,我們只需要創(chuàng)建新的 parser 類和 parserfactory 類,并且在 RuleConfigParserFactoryMap 類中胡桨,將新的 parser factory 對象添加到 cachedFactories 中即可官帘。代碼的改動非常少,基本上符合開閉原則昧谊。
實際上刽虹,對于規(guī)則配置文件解析這個應(yīng)用場景來說,工廠模式需要額外創(chuàng)建諸多 Factory類呢诬,也會增加代碼的復(fù)雜性涌哲,而且,每個 Factory 類只是做簡單的 new 操作尚镰,功能非常單狈Щ(只有一行代碼),也沒必要設(shè)計成獨立的類狗唉,所以初烘,在這個應(yīng)用場景下,簡單工廠模式簡單好用分俯,比工方法廠模式更加合適肾筐。
抽象工廠模式
抽象工廠模式也就是不僅生產(chǎn)鼠標,同時生產(chǎn)鍵盤澳迫。
也就是PC廠商是個父類局齿,有生產(chǎn)鼠標,生產(chǎn)鍵盤兩個接口橄登。
戴爾工廠抓歼,惠普工廠繼承它,可以分別生產(chǎn)戴爾鼠標+戴爾鍵盤拢锹,和惠普鼠標+惠普鍵盤谣妻。
創(chuàng)建工廠時,由戴爾工廠創(chuàng)建卒稳。
后續(xù)工廠.生產(chǎn)鼠標()則生產(chǎn)戴爾鼠標蹋半,工廠.生產(chǎn)鍵盤()則生產(chǎn)戴爾鍵盤。
在抽象工廠模式中充坑,假設(shè)我們需要增加一個工廠
假設(shè)我們增加華碩工廠减江,則我們需要增加華碩工廠,和戴爾工廠一樣捻爷,繼承PC廠商辈灼。
之后創(chuàng)建華碩鼠標,繼承鼠標類也榄。創(chuàng)建華碩鍵盤巡莹,繼承鍵盤類。
即可。
在抽象工廠模式中降宅,假設(shè)我們需要增加一個產(chǎn)品
假設(shè)我們增加耳麥這個產(chǎn)品骂远,則首先我們需要增加耳麥這個父類,再加上戴爾耳麥腰根,惠普耳麥這兩個子類激才。
之后在PC廠商這個父類中,增加生產(chǎn)耳麥的接口唠雕。最后在戴爾工廠贸营,惠普工廠這兩個類中,分別實現(xiàn)生產(chǎn)戴爾耳麥岩睁,惠普耳麥的功能钞脂。
以上。
總結(jié)
當創(chuàng)建邏輯比較復(fù)雜捕儒,是一個“大工程”的時候冰啃,我們就考慮使用工廠模式,封裝對象的創(chuàng) 建過程刘莹,將對象的創(chuàng)建和使用相分離阎毅。何為創(chuàng)建邏輯比較復(fù)雜呢?我總結(jié)了下面兩種情況点弯。
第一種情況:類似規(guī)則配置解析的例子扇调,代碼中存在 if-else 分支判斷,動態(tài)地根據(jù)不同類型創(chuàng)建不同的對象抢肛。針對這種情況狼钮,我們就考慮使用工廠模式,將這一大坨 if-else 創(chuàng)建對象的代碼抽離出來捡絮,放到工廠類中熬芜。
第二種情況,盡管我們不需要根據(jù)不同的類型創(chuàng)建不同的對象福稳,但是涎拉,單個對象本身 的創(chuàng)建過程比較復(fù)雜,比如前面提到的要組合其他類對象的圆,做各種初始化操作鼓拧。在這種情況下,我們也可以考慮使用工廠模式越妈,將對象的創(chuàng)建過程封裝到工廠類中
對于第一種情況毁枯,當每個對象的創(chuàng)建邏輯都比較簡單的時候,我推薦使用簡單工廠模式叮称,將 多個對象的創(chuàng)建邏輯放到一個工廠類中。當每個對象的創(chuàng)建邏輯都比較復(fù)雜的時候,為了避 免設(shè)計一個過于龐大的簡單工廠類瓤檐,我推薦使用工廠方法模式赂韵,將創(chuàng)建邏輯拆分得更細,每個對象的創(chuàng)建邏輯獨立到各自的工廠類中挠蛉。同理祭示,對于第二種情況,因為單個對象本身的創(chuàng) 建邏輯就比較復(fù)雜谴古,所以,我建議使用工廠方法模式掰担。
除了剛剛提到的這幾種情況之外汇陆,如果創(chuàng)建對象的邏輯并不復(fù)雜,那我們就直接通過 new 來創(chuàng)建對象就可以了带饱,不需要使用工廠模式毡代。