Android開發(fā)教程——設(shè)計模式之工廠模式

這篇文章將解決你以下幾個疑問:

  • 工廠模式分為哪幾類厢拭?
  • 每種模式具體概念及使用
  • 工廠模式使用場景是什么?

工廠模式分為哪幾類

一般情況下玷坠,工廠模式分為三種更加細(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)推薦

本文轉(zhuǎn)自 https://juejin.cn/post/7040671075427942437塘雳,如有侵權(quán),請聯(lián)系刪除普筹。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末败明,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子太防,更是在濱河造成了極大的恐慌妻顶,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜒车,死亡現(xiàn)場離奇詭異讳嘱,居然都是意外死亡,警方通過查閱死者的電腦和手機酿愧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門沥潭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嬉挡,你說我怎么就攤上這事叛氨。” “怎么了棘伴?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵寞埠,是天一觀的道長。 經(jīng)常有香客問我焊夸,道長仁连,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮饭冬,結(jié)果婚禮上使鹅,老公的妹妹穿的比我還像新娘。我一直安慰自己昌抠,他們只是感情好患朱,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著炊苫,像睡著了一般裁厅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上侨艾,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天执虹,我揣著相機與錄音,去河邊找鬼唠梨。 笑死袋励,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的当叭。 我是一名探鬼主播茬故,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蚁鳖!你這毒婦竟也來了磺芭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤才睹,失蹤者是張志新(化名)和其女友劉穎徘跪,沒想到半個月后甘邀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體琅攘,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年松邪,在試婚紗的時候發(fā)現(xiàn)自己被綠了坞琴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡逗抑,死狀恐怖剧辐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情邮府,我是刑警寧澤荧关,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站褂傀,受9級特大地震影響忍啤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜仙辟,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一同波、第九天 我趴在偏房一處隱蔽的房頂上張望鳄梅。 院中可真熱鬧,春花似錦未檩、人聲如沸戴尸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽孙蒙。三九已至,卻和暖如春筒溃,著一層夾襖步出監(jiān)牢的瞬間马篮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工怜奖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留浑测,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓歪玲,卻偏偏與公主長得像迁央,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子滥崩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容