設計模式系列-------工廠模式和抽象工廠模式

工廠模式(Factory Pattern)是 Java 中最常用的設計模式之一。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式。工廠方法原理很簡單,通過把創建對象的邏輯抽離出來,並對外提供一個工廠類的方式,讓類的職責更加單一,代碼更加清晰。使用者不需要關心具體對象初始化細節,只需要知道傳入工廠類的參數。工廠方式一般可以分爲三種:簡單工廠模式,工廠模式,抽象工廠模式。一般文章或者書會把簡單工廠模式和工廠模式當作一種設計模式,把抽象工廠模式當作另一種設計模式。本文通過一個解析配置文件的例子來演示不同工廠模式之間到底有什麼區別。

內容總結:

案例介紹

簡單工廠模式

工廠模式

工廠模式和簡單工廠模式的選擇

抽象工廠

三種工廠對比

開源代碼中的工廠模式


 

案例介紹

現在有一個需求,我們根據配置文件的後綴(xml,json,yaml等),選擇不同的解析器(JsonRuleConfigParser、XmlRuleConfigParser……),將文件中的配置解析成配置對象RuleConfig。 下面提供了一中最直接的寫法,在方法中直接根據文件的後綴,生成對應的解析器,然後將配置文件解析爲RuleConfig對象。你可以發現,這種寫法代碼的可閱讀性和拓展性都很差,包括中間一大段的if-else語句,以及如果想要添加新的後綴,那我就要改動對應的代碼。

/**
* 生成對應的解釋器跟業務代碼耦合
*/
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";
  }
}

/**
* 以下是解析器對應的代碼,具體ConfigParser如何解析文件,生成RuleConfig的功能省略
*/
public interface IRuleConfigParser {
  RuleConfig parse(String configText);
}

public class JsonRuleConfigParser implements IRuleConfigParser {
  @Override
  public RuleConfig parse(String configText) {
    return new RuleConfig ();
  }
}

public class XmlRuleConfigParser implements IRuleConfigParser {
  @Override
  public RuleConfig parse(String configText) {
    return new RuleConfig ();
  }
}

public class YamlRuleConfigParser implements IRuleConfigParser {
  @Override
  public RuleConfig parse(String configText) {
    return new RuleConfig ();
  }
}

public class PropertiesRuleConfigParser implements IRuleConfigParser {
  @Override
  public RuleConfig parse(String configText) {
    return new RuleConfig ();
  }
}

簡單工廠模式

可能你會想到把中間的if-else重新封裝成一個方法,這種思路跟簡單工廠模式很像,只不過簡單工廠模式做到更加徹底,他會將if-else部分的邏輯剝離到一個獨立的類中,讓這個類負責對象的創建。這種寫法可以讓類的職責更加單一,代碼更加清晰。對於簡單工廠模式來說,只有一個工廠類,如果需要新的解析器的時候,修改對應的工廠方法,就可以滿足需求。適合用於功能變化不多的場景。


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;
  }
}

/**
* 寫法二:將要返回的對象先存入緩存中,然後根據傳入參數直接返回,避免了生成和回收時的資源浪費
*/

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;
  }
}

工廠模式

工廠模式和簡單工廠模式相差不大,這兩者區別在於,工廠模式不只提供一個統一的工廠類(簡單工廠模式中的RuleConfigParserFactory ),而是提供一個抽象的手機代工廠,並且針對不同的對象再提供不同的工廠類。當參數傳入時,抽象工廠方法會根據傳入的參數,選擇對應的工廠類。然後通過對應的工廠類生成對應的對象。原來的工具類會被拆分成以下代碼:

/**
* 針對不同的解釋器,創建不同的工廠類。
*/
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();
  }
}

/**
* 簡單工廠模式是直接new 對象返回,而現在修改爲new 對應的工廠,
* 然後調用工廠方法返回對應的對象
*/
public class RuleConfigParserFactory {
  public static IRuleConfigParser createParser(String configFormat) {
    IRuleConfigParserFactory parserFactory = null;
    if ("json".equalsIgnoreCase(configFormat)) {
      parserFactory = new JsonRuleConfigParserFactory();
    } else if ("xml".equalsIgnoreCase(configFormat)) {
      parserFactory = new XmlRuleConfigParserFactory();
    } else if ("yaml".equalsIgnoreCase(configFormat)) {
      parserFactory = new YamlRuleConfigParserFactory();
    } else if ("properties".equalsIgnoreCase(configFormat)) {
      parserFactory = new PropertiesRuleConfigParserFactory();
    }
    return parserFactory.createParser;
  }
}

工廠模式和簡單工廠模式的選擇

你可能覺得簡單工廠模式和工廠模式沒什麼區別,而且按照工廠模式的寫法,功能上沒什麼變化,但是增加了很多工廠類,提高了設計的複雜度。那我們爲什麼要使用工廠模式呢?網上有些答案說簡單工廠模式不利於拓展,每添加一個類都要修改工廠類,違背了開閉原則,或者說工廠模式可以讓我們不需要知道具體對象類型,只要知道對應的工廠類就可以。

我覺得這些都不是主要原因。因爲無論簡單工廠模式還是工廠模式在新增一個類的時候,都要修改對應的工廠類或者抽象工廠類,把新增的對象或者工廠類添加進去。此外,這兩種方式都隱藏了具體類的實現方式,只不過工廠模式隱藏的更深。我覺得選擇工廠模式還簡單工廠模式的關鍵在於可創建對象的數量以及創建對象的複雜程度。在本文的例子中,可創建的類型不多(根據後綴,現在只有4種),而且創建的方式也很簡單(只需要new就可創建對應的對象)。但是,如果我們要創建的對象種類有很多(假設有十幾種),而且創建的方式很複雜(需要十幾或者幾十行代碼),那麼,當你選擇使用簡單工廠模式的時,你的工廠類就會很複雜,代碼邏輯不清晰。而使用工廠模式,把對應複雜的對象創建的過程封裝到對應的工廠類中。保證了代碼的可讀性,以及讓類的職責更加單一。

所以,選擇工廠模式還是簡單工廠模式要根據業務場景。如果業務很簡單(類似文中這樣),那麼簡單工廠模式會比工廠模式更好理解和書寫,如果業務很複雜,那麼工廠模式會比簡單工廠模式更好理解。

抽象工廠

抽象工廠可以理解爲是工廠方法的拓展,在上面的例子,我們根據解析文件的類型,生成了四個工廠類,每個類中固定返回IRuleConfigParser類的對象。如果我們的需求變了,除了將文件解析成IRuleConfigParser類,我們還要支持將文件解析成另一種類ISystemConfigParser。如果我們將以前的做法,可以再新增四個工廠類,每個工廠類返回對應的ISystemConfigParser。

針對規則配置的解析器:基於接口IRuleConfigParser
JsonRuleConfigParser
XmlRuleConfigParser
YamlRuleConfigParser
PropertiesRuleConfigParser

針對系統配置的解析器:基於接口ISystemConfigParser
JsonSystemConfigParser
XmlSystemConfigParser
YamlSystemConfigParser
PropertiesSystemConfigParser

如果使用抽象工廠,可以通過對需求進行分析,將類似的功能彙總成一個新的抽象類,從而減少類的數量

/**
* 抽象工廠其實就是一個類中定義多個接口,然後所有的工廠類都實現對應的功能
*/
public interface IConfigParserFactory {
  IRuleConfigParser createRuleParser();
  ISystemConfigParser createSystemParser();
  //此處可以擴展新的parser類型,比如IBizConfigParser
}

public class JsonConfigParserFactory implements IConfigParserFactory {
  @Override
  public IRuleConfigParser createRuleParser() {
    return new JsonRuleConfigParser();
  }

  @Override
  public ISystemConfigParser createSystemParser() {
    return new JsonSystemConfigParser();
  }
}

public class XmlConfigParserFactory implements IConfigParserFactory {
  @Override
  public IRuleConfigParser createRuleParser() {
    return new XmlRuleConfigParser();
  }

  @Override
  public ISystemConfigParser createSystemParser() {
    return new XmlSystemConfigParser();
  }
}

// 省略YamlConfigParserFactory和PropertiesConfigParserFactory代碼

個人理解抽象工廠是對相當於對工廠模式進行歸納彙總,從而減少創建的類。但是如果出現了一種新的,但是不在我們之前歸納方式之內的類,那麼抽象工廠就不太好拓展。

三種工廠對比

簡單工廠模式:適合用於創建的類少,並且創建方式比較簡單的場合。

工廠模式:適合用於創建類多,並且創建方式比較複雜,寫在一個類中會讓類很難理解的場合。

抽象工廠:適合用於要創建的類之間存在一定的關係(例如文中將通過同一後綴的文件,生成不同類的方法定義在一個工廠內),而且這個關係不太容易變化的場景。

開源代碼中的工廠模式

很多開源代碼都用到了工廠模式,例如JDK中的 Calendar、DateFormat ,URLStreamHandlerFactory,Collections, Executors等類,Spring 的BeanFactory類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章