Skip to content

设计模式:创建型—工厂模式

Published: at 21:30:15

一般情况下,工厂模式被分为三种更加细分的模式:简单工厂、工厂方法和抽象工厂

简单工厂(Simple Factory)

定义

简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

代码示例

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

在上面的代码中,通过load方法将指定目录的配置解析成Java对象RuleConfig并返回,其中RuleConfigParserFactory提供的静态方法createParser实现通过不同的文件后缀来选择不同的解析器;RuleConfigParserFactory类就是一个简单工厂模式类。

在简单工厂模式实现中,有很多if-else分支判断,严格来说是违背了设计原则种的开闭原则(添加一个新的功能应该在已有代码基础上扩展代码而非修改已有代码),但是在权衡扩展性和可读性时,这样的代码在大多数情况下(parser数量固定)也是没有任何问题的。

工厂方法(Factory Method)

如果我们非得要将 上面简单工厂种的 if 分支逻辑去掉,那应该怎么办呢?

比较经典处理方法就是利用多态。

按照多态的实现思路,对上面的代码进行重构,重构之后的代码如下所示:

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

重构之后的代码实际上就是工厂方法模式的典型代码实现。

以后当我们需要增加一种parser的时候,只需要新增一个实现了IRuleConfigParserFactory 接口的类即可。这种方式也更加符合前面说到的开闭原则。

上面的代码理论上看起来还不错,但是实际上使用还是有些许问题的,下面是具体的使用代码:

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

从上面使用代码我们可以发现,在代码中又出现了很多if判断分支,主要是用来创建不通的IRuleConfigParserFactory,那这种情况下我们又怎么优化呢?

我们可以再为我们的工厂类创建一个简单工厂,也就是用来创建工厂的工厂就可以了。

下面是代码实现:

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

//因为工厂类只包含方法,不包含成员变量,完全可以复用,
//不需要每次都创建新的工厂类对象,所以,简单工厂模式的第二种实现思路更加合适。
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;
  }
}

上面的getParserFactory方法就是新加的简单工厂实现了,只是实现方式和最开始上面介绍的简单工厂实现有一些区别,主要是我们不需要每次都创建新的工厂类对象,完全可以做到全局共用,类似单例对象,所以我们把具体的工厂都换成到了Map中。

当我们以后需要添加新的规则配置解析器的时候,我们只需要创建新的 parser 类和 parser factory 类,并且在 RuleConfigParserFactoryMap 类中,将新的 parser factory 对象添加到 cachedFactories 中即可。

这样代码的改动非常少,基本上也符合开闭原则。

抽象工厂(Abstract Factory)

在上面的介绍中我们可以看出简单工厂和工厂方法都是按照文件格式(Json、Xml、Yaml……)来进行分类的,如果我们以后要再新加一种分类方式,对应的parser类也就要做成倍的增加。

下面是假如新增一种按照系统配置的解析器需要的类:

针对规则配置的解析器:基于接口IRuleConfigParser
JsonRuleConfigParser
XmlRuleConfigParser
YamlRuleConfigParser
PropertiesRuleConfigParser

针对系统配置的解析器:基于接口ISystemConfigParser
JsonSystemConfigParser
XmlSystemConfigParser
YamlSystemConfigParser
PropertiesSystemConfigParser

我们可以发现针对这种多分类的场景,如果使用工厂方法模式实现的话,就需要编写8哥对应的不同的工厂类,如果还需要更多的分类,那也就还需要再增加工厂类;

针对这样的场景,抽象工厂就产生了,我们可以让一个工厂可以创建多个不同类型的对象(IRuleConfigParser、ISystemConfigParser 等),而不是只创建一种同类型的对象,这样就可以有效的减少工厂的个数。

代码示例如下:

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代码

如何选择使用对应的工厂

在我们代码逻辑过于复杂时,基于代码的可读性,我们将代码进行剥离成独立的函数或者类,但是如果代码并不复杂时,我们也就毋须这样做了。

基于这个思想,当创建对象的代码比较简单时,比如只是简单的new一下,那我们可以使用简单工厂;相反,如果创建对象的逻辑比较复杂时(需要初始化各种数据,不再是简单的new对象那么简单),那我们就可以考虑使用工厂方法了,将复杂的创建逻辑都拆分到不同的工厂中去,让每个独立的工厂创建逻辑不至于太过复杂,这就是简单工厂不能比的。

除此之外,如果简单工厂创建的对象是可以复用时,我们也可以选择第二种简单工厂的方式来实现,使用一个Map来缓存第一次创建好的对象。

抽象工厂模式的应用场景比较特殊,当我们需要根据不通的分类来创建对象时可以选择抽象工厂来实现。

工厂模式在Java Calendar类中的应用

工厂模式在 Java JDK 中的一个应用:java.util.Calendar。

从命名上,我们无法看出它是一个工厂类。

Calendar 类提供了大量跟日期相关的功能代码,同时,又提供了一个 getInstance() 工厂方法,用来根据不同的 TimeZone 和 Locale 创建不同的 Calendar 子类对象。

在这里Calendar 的功能代码和工厂方法代码都耦合在了一个类中所以,它并没有以 Factory 作为后缀来命名。因此它也不单单是一个工厂类。

从代码中,我们可以看出,getInstance() 方法可以根据不同 TimeZone 和 Locale,创建不同的 Calendar 子类对象,比如 BuddhistCalendarJapaneseImperialCalendarGregorianCalendar

这些细节完全封装在工厂方法中,使用者只需要传递当前的时区和地址,就能够获得一个 Calendar 类对象来使用,而获得的对象具体是哪个 Calendar 子类的对象,使用者在使用的时候并不关心。

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
  //...
  public static Calendar getInstance(TimeZone zone, Locale aLocale){
    return createCalendar(zone, aLocale);
  }

  private static Calendar createCalendar(TimeZone zone,Locale aLocale) {
    CalendarProvider provider = LocaleProviderAdapter.getAdapter(
        CalendarProvider.class, aLocale).getCalendarProvider();
    if (provider != null) {
      try {
        return provider.getInstance(zone, aLocale);
      } catch (IllegalArgumentException iae) {
        // fall back to the default instantiation
      }
    }

    Calendar cal = null;
    if (aLocale.hasExtensions()) {
      String caltype = aLocale.getUnicodeLocaleType("ca");
      if (caltype != null) {
        switch (caltype) {  //创建不同的子类返回
          case "buddhist":
            cal = new BuddhistCalendar(zone, aLocale);
            break;
          case "japanese":
            cal = new JapaneseImperialCalendar(zone, aLocale);
            break;
          case "gregory":
            cal = new GregorianCalendar(zone, aLocale);
            break;
        }
      }
    }
    if (cal == null) {
      if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
        cal = new BuddhistCalendar(zone, aLocale);
      } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja" && aLocale.getCountry() == "JP") {
        cal = new JapaneseImperialCalendar(zone, aLocale);
      } else {
        cal = new GregorianCalendar(zone, aLocale);
      }
    }
    return cal;
  }
  //...
}