這篇文章將解決你以下幾個疑問:
- 工廠模式分為哪幾類厢拭?
- 每種模式具體概念及使用
- 工廠模式使用場景是什么?
工廠模式分為哪幾類
一般情況下玷坠,工廠模式分為三種更加細(xì)分的類型:簡單工廠蜗搔、工廠方法和抽象工廠。不過八堡,在 GoF 的《設(shè)計模式》一書中樟凄,它將簡單工廠模式看作是工廠方法模式的一種特例,所以工廠模式只被分成了工廠方法和抽象工廠兩類兄渺。
簡單工廠缝龄、工廠方法原理比較簡單,在實際的項目中也比較常用挂谍。而抽象工廠的原理稍微復(fù)雜點叔壤,在實際的項目中相對也不常用。
簡單工廠(Simple Factory)
定義:一個工廠方法口叙,依據(jù)傳入的參數(shù)百新,生成對應(yīng)的產(chǎn)品對象;我們通過一個例子來解釋一下庐扫。
public class RuleConfigSource {
public RuleConfig load(String ruleConfigFilePath) {
String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
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);
}
String configText = "";
//從ruleConfigFilePath文件中讀取配置文本到configText中
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(String filePath) {
//...解析文件名獲取擴展名饭望,比如rule.json仗哨,返回json
return "json";
}
}
為了讓類的職責(zé)更加單一、代碼更加清晰铅辞,我們還可以進(jìn)一步將 createParser() 函數(shù)剝離到一個獨立的類中厌漂,讓這個類只負(fù)責(zé)對象的創(chuàng)建。而這個類就是我們現(xiàn)在要講的簡單工廠模式類斟珊。具體的代碼如下所示:
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) {
//...解析文件名獲取擴展名苇倡,比如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;
}
}
我們每次調(diào)用 RuleConfigParserFactory 的 createParser() 的時候囤踩,都要創(chuàng)建一個新的 parser旨椒。實際上,如果 parser 可以復(fù)用堵漱,為了節(jié)省內(nèi)存和對象創(chuàng)建的時間综慎,我們可以將 parser 事先創(chuàng)建好緩存起來。當(dāng)調(diào)用 createParser() 函數(shù)的時候勤庐,我們從緩存中取出 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全憑你自己說了算
}
IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase());
return parser;
}
}
,盡管簡單工廠模式的代碼實現(xiàn)中愉镰,有多處 if 分支判斷邏輯米罚,違背開閉原則,但權(quán)衡擴展性和可讀性丈探,這樣的代碼實現(xiàn)在大多數(shù)情況下(比如录择,不需要頻繁地添加 parser,也沒有太多的 parser)是沒有問題的碗降。
工廠方法(Factory Method)
如果我們非得要將 if 分支邏輯去掉糊肠,那該怎么辦呢?比較經(jīng)典處理方法就是利用多態(tài)遗锣。按照多態(tài)的實現(xiàn)思路货裹,對上面的代碼進(jì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 IRuleConfigParserFactory {
@Override
public IRuleConfigParser createParser() {
return new PropertiesRuleConfigParser();
}
}
這樣當(dāng)我們新增一種 parser 的時候精偿,只需要新增一個實現(xiàn)了 IRuleConfigParserFactory 接口的 Factory 類即可弧圆。所以,工廠方法模式比起簡單工廠模式更加符合開閉原則笔咽。
我們看一下搔预,如何用這些工廠類來實現(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 supported: " + ruleConfigFilePath);
}
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";
}
}
工廠類對象的創(chuàng)建邏輯又耦合進(jìn)了 load() 函數(shù)中,跟我們最初的代碼版本非常相似甩十,引入工廠方法非但沒有解決問題船庇,反倒讓設(shè)計變得更加復(fù)雜了吭产。那怎么來解決這個問題呢?
我們可以為工廠類再創(chuàng)建一個簡單工廠鸭轮,也就是工廠的工廠臣淤,用來創(chuàng)建工廠類對象。
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) {
//...解析文件名獲取擴展名,比如rule.json按厘,返回json
return "json";
}
}
//因為工廠類只包含方法医吊,不包含成員變量,完全可以復(fù)用逮京,
//不需要每次都創(chuàng)建新的工廠類對象卿堂,所以,簡單工廠模式的第二種實現(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;
}
}
當(dāng)我們需要添加新的規(guī)則配置解析器的時候,我們只需要創(chuàng)建新的 parser 類和 parser factory 類麦箍,并且在 RuleConfigParserFactoryMap 類中漓藕,將新的 parser factory 對象添加到 cachedFactories 中即可。代碼的改動非常少挟裂,基本上符合開閉原則享钞。
那什么時候該用工廠方法模式,而非簡單工廠模式呢诀蓉?
我們前面提到栗竖,之所以將某個代碼塊剝離出來,獨立為函數(shù)或者類渠啤,原因是這個代碼塊的邏輯過于復(fù)雜狐肢,剝離之后能讓代碼更加清晰,更加可讀沥曹、可維護(hù)份名。但是,如果代碼塊本身并不復(fù)雜妓美,就幾行代碼而已僵腺,我們完全沒必要將它拆分成單獨的函數(shù)或者類。
基于這個設(shè)計思想壶栋,當(dāng)對象的創(chuàng)建邏輯比較復(fù)雜辰如,不只是簡單的 new 一下就可以,而是要組合其他類對象贵试,做各種初始化操作的時候琉兜,我們推薦使用工廠方法模式凯正,將復(fù)雜的創(chuàng)建邏輯拆分到多個工廠類中,讓每個工廠類都不至于過于復(fù)雜呕童。而使用簡單工廠模式漆际,將所有的創(chuàng)建邏輯都放到一個工廠類中,會導(dǎo)致這個工廠類變得很復(fù)雜夺饲。
除此之外奸汇,在某些場景下,如果對象不可復(fù)用往声,那工廠類每次都要返回不同的對象擂找。如果我們使用簡單工廠模式來實現(xiàn),就只能選擇第一種包含 if 分支邏輯的實現(xiàn)方式浩销。如果我們還想避免煩人的 if-else 分支邏輯贯涎,這個時候,我們就推薦使用工廠方法模式慢洋。
相關(guān)推薦
【2021 最新版】Android studio全套教程+Android(安卓)開發(fā)入門到精通(項目實戰(zhàn)篇)_嗶哩嗶哩_bilibili
Android開發(fā)進(jìn)階學(xué)習(xí)—設(shè)計思想解讀開源框架 · 已更新至104集(持續(xù)更新中~)_嗶哩嗶哩_bilibili
Android音視頻開發(fā):音視頻基礎(chǔ)知識到直播推流實戰(zhàn)系列教程_嗶哩嗶哩_bilibili
Android項目實戰(zhàn)-從0開始手把手實現(xiàn)組件化路由SDK項目實戰(zhàn)_嗶哩嗶哩_bilibili
本文轉(zhuǎn)自 https://juejin.cn/post/7040671075427942437塘雳,如有侵權(quán),請聯(lián)系刪除普筹。