02、工廠模式

簡單工廠模式(靜態(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() 返回的是緩存好的單例工廠對象队魏。

image

當我們需要添加新的規(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)戴爾鍵盤。

image

在抽象工廠模式中充坑,假設(shè)我們需要增加一個工廠

假設(shè)我們增加華碩工廠减江,則我們需要增加華碩工廠,和戴爾工廠一樣捻爷,繼承PC廠商辈灼。

之后創(chuàng)建華碩鼠標,繼承鼠標類也榄。創(chuàng)建華碩鍵盤巡莹,繼承鍵盤類。

即可。

image

在抽象工廠模式中降宅,假設(shè)我們需要增加一個產(chǎn)品

假設(shè)我們增加耳麥這個產(chǎn)品骂远,則首先我們需要增加耳麥這個父類,再加上戴爾耳麥腰根,惠普耳麥這兩個子類激才。

之后在PC廠商這個父類中,增加生產(chǎn)耳麥的接口唠雕。最后在戴爾工廠贸营,惠普工廠這兩個類中,分別實現(xiàn)生產(chǎn)戴爾耳麥岩睁,惠普耳麥的功能钞脂。

以上。

image

總結(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)建對象就可以了带饱,不需要使用工廠模式毡代。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市勺疼,隨后出現(xiàn)的幾起案子教寂,更是在濱河造成了極大的恐慌,老刑警劉巖执庐,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酪耕,死亡現(xiàn)場離奇詭異,居然都是意外死亡轨淌,警方通過查閱死者的電腦和手機迂烁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來猿诸,“玉大人婚被,你說我怎么就攤上這事∈崴洌” “怎么了址芯?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長窜觉。 經(jīng)常有香客問我谷炸,道長,這世上最難降的妖魔是什么禀挫? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任旬陡,我火速辦了婚禮,結(jié)果婚禮上语婴,老公的妹妹穿的比我還像新娘描孟。我一直安慰自己驶睦,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布匿醒。 她就那樣靜靜地躺著场航,像睡著了一般。 火紅的嫁衣襯著肌膚如雪廉羔。 梳的紋絲不亂的頭發(fā)上溉痢,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音憋他,去河邊找鬼孩饼。 笑死,一個胖子當著我的面吹牛竹挡,可吹牛的內(nèi)容都是我干的镀娶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼此迅,長吁一口氣:“原來是場噩夢啊……” “哼汽畴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起耸序,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤忍些,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后坎怪,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體罢坝,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年搅窿,在試婚紗的時候發(fā)現(xiàn)自己被綠了嘁酿。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡男应,死狀恐怖闹司,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沐飘,我是刑警寧澤游桩,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站耐朴,受9級特大地震影響借卧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜筛峭,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一铐刘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧影晓,春花似錦镰吵、人聲如沸檩禾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锌订。三九已至,卻和暖如春画株,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背啦辐。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工谓传, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芹关。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓续挟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親侥衬。 傳聞我的和親對象是個殘疾皇子诗祸,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354