基础知识回顾

工厂模式包括:简单工厂(不在23种设计模式中)、工厂方法和抽象工厂。

简单工厂

结构

定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类。
image.png

  • 抽象类或接口:定义了要创建的产品对象的接口。
  • 具体实现:具有统一父类的具体类型的产品。
  • 产品工厂:负责创建产品对象。工厂模式同样体现了开闭原则,将“创建具体的产品实现类”这部分变化的代码从不变化的代码“使用产品”中分离出来,之后想要新增产品时,只需要扩展工厂的实现即可。

    使用

    现在举一个文件解析的例子 ```java

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

  1. String configText = "";
  2. //从ruleConfigFilePath文件中读取配置文本到configText中
  3. RuleConfig ruleConfig = parser.parse(configText);
  4. 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; } }

在上面的代码实现中,我们每次调用 RuleConfigParserFactory 的 createParser() 的时候,都要创建一个新的 parser。实际上,如果 parser 可以复用,为了节省内存和对象创建的时间,我们可以将 parser 事先创建好缓存起来。当调用 createParser() 函数的时候,我们从缓存中取出 parser 对象直接使用。
```java

/**
* 优化版本
*/

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,而非接口或者抽象类,createParser()方法利用if-else创建并返回具体的解析类,如果增加新的解析类类,工厂的创建方法中就要增加新的if-else。这种做法扩展性差,违背了开闭原则,也影响了可读性。所以,这种方式使用在业务较简单,工厂类不会经常更改的情况。

工厂方法

为了解决上面提到的”增加if-else”的问题,可以为每一个键盘子类建立一个对应的工厂子类,这些工厂子类实现同一个抽象工厂接口。这样,创建不同品牌的键盘,只需要实现不同的工厂子类。当有新品牌加入时,新建具体工厂继承抽象工厂,而不用修改任何一个类

结构

image.png

  • 抽象工厂:声明了工厂方法的接口。
  • 具体产品工厂:实现工厂方法的接口,负责创建产品对象。
  • 产品抽象类或接口:定义工厂方法所创建的产品对象的接口。
  • 具体产品实现:具有统一父类的具体类型的产品。

    使用

    ```java

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 接口的 Factory 类即可。所以,工厂方法模式比起简单工厂模式更加符合开闭原则。<br />接下来,我们看一下,如何用这些工厂类来实现 RuleConfigSource 的 load() 函数。具体的代码如下所示:
```java

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

从上面的代码实现来看,工厂类对象的创建逻辑又耦合进了 load() 函数中,跟我们最初的代码版本非常相似,引入工厂方法非但没有解决问题,反倒让设计变得更加复杂了。那怎么来解决这个问题呢?我们可以为工厂类再创建一个简单工厂,也就是工厂的工厂,用来创建工厂类对象。这段话听起来有点绕,我把代码实现出来了,你一看就能明白了。其中,RuleConfigParserFactoryMap 类是创建工厂对象的工厂类,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";
  }
}

//因为工厂类只包含方法,不包含成员变量,完全可以复用,
//不需要每次都创建新的工厂类对象,所以,简单工厂模式的第二种实现思路更加合适。
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;
  }
}
当我们需要添加新的规则配置解析器的时候,我们只需要创建新的 parser 类和 parser factory 类,并且在 RuleConfigParserFactoryMap 类中,将新的 parser factory 对象添加到 cachedFactories 中即可。代码的改动非常少,基本上符合开闭原则。<br />    实际上,对于规则配置文件解析这个应用场景来说,工厂模式需要额外创建诸多 Factory 类,也会增加代码的复杂性,而且,每个 Factory 类只是做简单的 new 操作,功能非常单薄(只有一行代码),也没必要设计成独立的类,所以,在这个应用场景下,简单工厂模式简单好用,比工厂方法模式更加合适。<br />    那什么时候该用工厂方法模式,而非简单工厂模式呢?
  1. 当对象的创建逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作的时候,我们推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂。
  2. 在某些场景下,如果对象不可复用,那工厂类每次都要返回不同的对象。如果我们使用简单工厂模式来实现,就只能选择第一种包含 if 分支逻辑的实现方式。如果我们还想避免烦人的 if-else 分支逻辑,这个时候,我们就推荐使用工厂方法模式。

    抽象工厂

    结构

    为了缩减工厂实现子类的数量,不必给每一个产品分配一个工厂类,可以将产品进行分组,每组中的不同产品由同一个工厂类的不同方法来创建。
    image.png
  • 抽象工厂:声明了创建抽象产品对象的操作接口。
  • 具体产品工厂:实现了抽象工厂的接口,负责创建产品对象。
  • 产品抽象类或接口:定义一类产品对象的接口。
  • 具体产品实现:定义一个将被相应具体工厂创建的产品对象。

    使用

    在简单工厂和工厂方法中,类只有一种分类方式。比如,在规则配置解析那个例子中,解析器类只会根据配置文件格式(Json、Xml、Yaml……)来分类。但是,如果类有两种分类方式,比如,我们既可以按照配置文件格式来分类,也可以按照解析的对象(Rule 规则配置还是 System 系统配置)来分类,那就会对应下面这 8 个 parser 类。如果我们未来还需要增加针对业务配置的解析器(比如 IBizConfigParser),那就要再对应地增加 4 个工厂类。 ```java

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

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

   我们可以让一个工厂负责创建多个不同类型的对象(IRuleConfigParser、ISystemConfigParser 等),而不是只创建一种 parser 对象。这样就可以有效地减少工厂类的个数。具体的代码实现如下所示:
```java

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

优缺点

增加分组非常简单,分组中的产品扩展非常困难.

Spring下的最佳实践

 工厂模式一般配合策略模式一起使用,当系统中有多种产品(策略),且每种产品有多个实例时,此时适合使用工厂模式:每种产品对应的工厂提供该产品不同实例的创建功能,从而避免调用方和产品创建逻辑的耦合,完美符合迪米特法则(最少知道原则)。

使用工厂方法模式

背景

在平常开发中,我们经常会在 Spring 中实现诸如这样的功能:收集某一类具有共同特征的 Bean(都实现了某个接口或者都打上了某个注解等),然后放入容器中(一般是 Map),使用的时候根据 Bean 的标识,来获取到对应的 Bean。
通过表单标识获得表单对应提交处理器的 FormDataHandlerFactory:

@Component
public class FormDataHandlerFactory {

    private static final 
    Map<String, FormDataHandler> FORM_DATA_HANDLER_MAP = new HashMap<>(16);

    /**
     * 根据表单标识,获取对应的 Handler
     *
     * @param formCode 表单标识
     * @return 表单对应的 Handler
     */
    public FormDataHandler getHandler(String formCode) {
        return FORM_DATA_HANDLER_MAP.get(formCode);
    }

    @Autowired
    public void setFormDataHandlers(List<FormDataHandler> handlers) {
        for (FormDataHandler handler : handlers) {
            FORM_DATA_HANDLER_MAP.put(handler.getFormCode(), handler);
        }
    }
}

通过表单项类型获得表单项转换器的 FormItemConverterFactory:


@Component
public class FormItemConverterFactory {

    private static final 
    EnumMap<FormItemTypeEnum, FormItemConverter> CONVERTER_MAP = new EnumMap<>(FormItemTypeEnum.class);

    /**
     * 根据表单项类型获得对应的转换器
     *
     * @param type 表单项类型
     * @return 表单项转换器
     */
    public FormItemConverter getConverter(FormItemTypeEnum type) {
        return CONVERTER_MAP.get(type);
    }

    @Autowired
    public void setConverters(List<FormItemConverter> converters) {
        for (final FormItemConverter converter : converters) {
            CONVERTER_MAP.put(converter.getType(), converter);
        }
    }
}

上面两段代码,其实就是基于Spring的简单工厂模式,如果再来一个业务类型就要再新增一个工厂类,我们下面尝试基于Spring优化一下

方案

对于工厂方法模式来说,变化的是产品、工厂,因而我们可以先定义出抽象的产品和抽象的工厂。
抽象的产品(策略):


public interface Strategy<T> {

    /**
     * 获得策略的标识
     */
    T getId();
}

每个产品必须实现 Strategy 接口,代表每个产品必须有一个唯一的标识。
抽象的策略工厂:

public abstract class StrategyFactory<T, S extends Strategy<T>> 
                implements InitializingBean, ApplicationContextAware {

    private Map<T, S> strategyMap;

    private ApplicationContext appContext;

    /**
     * 根据策略 id 获得对应的策略的 Bean
     *
     * @param id 策略 id
     * @return 策略的 Bean
     */
    public S getStrategy(T id) {
        return strategyMap.get(id);
    }

    /**
     * 获取策略的类型(交给子类去实现)
     *
     * @return 策略的类型
     */
    protected abstract Class<S> getStrategyType();

    @Override
    public void afterPropertiesSet() {
        // 获取 Spring 容器中,所有 S 类型的 Bean
        Collection<S> strategies = appContext.getBeansOfType(getStrategyType()).values();

        strategyMap = Maps.newHashMapWithExpectedSize(strategies.size());

        // 将所有 S 类型的 Bean 放入到 strategyMap 中
        for (final S strategy : strategies) {
            T id = strategy.getId();
            strategyMap.put(id, strategy);
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        appContext = applicationContext;
    }
}

Spring 容器在启动的时候,会去扫描工厂指定的类型(Class)的 Bean,并将其注册到工厂中(加入到 strategyMap)。所以对于工厂中产品的生产过程,借助 Spring。
接下来基于我们的抽象产品和抽象工厂,我们重构上面的两个 Factory:
通过表单标识获得表单对应提交处理器的 FormDataHandlerFactory


@Component
public class FormDataHandlerFactory extends StrategyFactory<String, FormDataHandler> {

    @Override
    protected Class<FormDataHandler> getStrategyType() {
        return FormDataHandler.class;
    }
}

FormDataHandlerFactory 只需要指定一下其产品类型为 FormDataHandler。当然,FormDataHandler 我们也需要改造一下:

public interface FormDataHandler extends Strategy<String> {

    @Override
    default String getId() { return getFormCode(); }

    String getFormCode();

    CommonResponse<Object> submit(FormSubmitRequest request);
}

通过表单项类型获得表单项转换器的 FormItemConverterFactory


@Component
public class FormItemConverterFactory extends StrategyFactory<FormItemTypeEnum, FormItemConverter> {

    @Override
    protected Class<FormItemConverter> getStrategyType() {
        return FormItemConverter.class;
    }
}

此时,FormItemConverterFactory 也只需要指定一下产品的类型,不再会写重复代码。同理,需要改造一下 FormItemConverter:


public interface FormItemConverter extends Strategy<FormItemTypeEnum> {

    @Override
    default FormItemTypeEnum getId() { return getType(); }

    FormItemTypeEnum getType();

    FormItem convert(FormItemConfig config);
}

image.png
如果这个时候新加一个 通过列表标识获得列表数据拉取器的 ListDataFetcherFactory,那么首先定义出获取列表数据的接口(产品):

public interface ListDataFetcher extends Strategy<String> {

    CommonResponse<JSONObject> fetchData(ListDataFetchRequest request);
}

然后再实现 ListDataFetcherFactory(工厂):

@Component
public class ListDataFetcherFactory extends StrategyFactory<String, ListDataFetcher> {

    @Override
    protected Class<ListDataFetcher> getStrategyType() {
        return ListDataFetcher.class;
    }
}

通过抽象产品 Strategy 和抽象工厂 StrategyFactory,我们的代码完美符合了 DRY 原则(Don’t Repeat Yourself)。

优化

借助反射

借助反射,我们还可以使得工厂代码变得更加简单:因为如果父类包含泛型参数,且子类对泛型参数进行了具体化,那么这个具体化的泛型类型,可在运行时获取到。基于这个特性,我们可以改造 StrategyFactory:

public abstract class StrategyFactory<T, S extends Strategy<T>>
                implements InitializingBean, ApplicationContextAware {

    ...

    /**
     * 通过反射获取策略的类型
     *
     * @return 策略的类型
     */
    protected Class<S> getStrategyType() {
        // getClass 获取当前运行时实例的类,getGenericSuperclass 获得泛型父类
        Type superclass = getClass().getGenericSuperclass();
        ParameterizedType pt = (ParameterizedType) superclass;
        Type[] actualTypeArguments = pt.getActualTypeArguments();
        // 获得索引为 1 的实际参数类型,即第二个实际参数的类型
        Type actualTypeArgument = actualTypeArguments[1];
        @SuppressWarnings("unchecked")
        Class<S> result = (Class<S>) actualTypeArgument;

        return result;
    }

    ...
}

那么上面三个 Factory 写起来就更简单了:

@Component
public class FormDataHandlerFactory extends StrategyFactory<String, FormDataHandler> {


}


@Component
public class FormItemConverterFactory extends StrategyFactory<FormItemTypeEnum, FormItemConverter> {
}


@Component
public class ListDataFetcherFactory extends StrategyFactory<String, ListDataFetcher> {
}

组合优先于继承

上述的方案是通过继承,并借助泛型的反射功能,由子类来指定策略( S getStrategyType)的类型。如果工厂类型较多,那么每次新加一个工厂类,容易导致 “类爆炸”。对于上述的方案,变化的部分就是策略的类型,除了继承,我们还可以通过组合来解决这个变化。修改我们的 StrategyFactory:


public class StrategyFactory<T, S extends Strategy<T>>
        implements InitializingBean, ApplicationContextAware {

    private final Class<S> strategyType;

    private Map<T, S> strategyMap;

    private ApplicationContext appContext;

    /**
     * 创建一个策略工厂
     * 
     * @param strategyType 策略的类型
     */
    public StrategyFactory(Class<S> strategyType) {
        this.strategyType = strategyType;
    }

    /**
     * 根据策略 id 获得对应的策略的 Bean
     *
     * @param id 策略 id
     * @return 策略的 Bean
     */
    public S getStrategy(T id) {
        return strategyMap.get(id);
    }

    @Override
    public void afterPropertiesSet() {
        // 获取 Spring 容器中,所有 S 类型的 Bean
        Collection<S> strategies = appContext.getBeansOfType(strategyType).values();

        strategyMap = Maps.newHashMapWithExpectedSize(strategies.size());

        // 将 所有 S 类型的 Bean 放入到 strategyMap 中
        for (final S strategy : strategies) {
            T id = strategy.getId();

            strategyMap.put(id, strategy);
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        appContext = applicationContext;
    }
}

此时 StrategyFactory 不再是抽象类,并且为 StrategyFactory 引入一个新的属性 strategyType,并且在构造 StrategyFactory 就必须设置当前工厂中的策略(产品)类型。那么对于 FormDataHandlerFactory、FormItemConverterFactory 和 ListDataFetcherFactory,我们不需要再通过继承产生,直接通过配置进行组合即可:

@Configuration
public class FactoryConfig {

    @Bean
    public StrategyFactory<String, FormDataHandler> formDataHandlerFactory() {
        return new StrategyFactory<>(FormDataHandler.class);
    }

    @Bean
    public StrategyFactory<FormItemTypeEnum, FormItemConverter> formItemConverterFactory() {
        return new StrategyFactory<>(FormItemConverter.class);
    }

    @Bean
    public StrategyFactory<String, ListDataFetcher> listDataFetcherFactory() {
        return new StrategyFactory<>(ListDataFetcher.class);
    }
}