23种经典设计模式-工厂模式-创建型


简介

工厂设计模式(Factory Design Pattern):
创建型设计模式,工厂模式分为三种更加细分的类型:简单/静态工厂模式、工厂方法模式和抽象工厂模式。

要点

什么是工厂?

工厂是一个含义模糊的术语,表示可以创建一些东西的函数、方法或类。
最常见的情况下,工厂创建的是对象。但是它们也可以创建文件和数据库记录等其他东西。
例如,下面这些东西都可以非正式地被称为工厂
创建程序 GUI 的函数或方法。
创建用户的类。
以特定方式调用类构造函数的静态方法。
一种创建型设计模式。

为什么我们需要工厂设计模式?

  1. 当你在编写代码的过程中,如果无法预知对象确切类别及其依赖关系时,可使用工厂方法

    工厂方法将创建产品的代码与实际使用产品的代码分离,从而能在不影响其他代码的情况下扩展产品创建部分代码。

  2. 如果你希望用户能扩展你软件库或框架的内部组件,可使用工厂方法

    继承可能是扩展软件库或框架默认行为的最简单方法。但是当你使用子类替代标准组件时,框架如何辨识出该子类?

    解决方案是将各框架中构造组件的代码集中到单个工厂方法中,并在继承该组件之外允许任何人对该方法进行重写。

  3. 如果你希望复用现有对象来节省系统资源,而不是每次都重新创建对象,可使用工厂方法

    在处理大型资源密集型对象(比如数据库连接、文件系统和网络资源)时,你会经常碰到这种资源需求:
    首先,你需要创建存储空间来存放所有已经创建的对象。
    当他人请求一个对象时,程序将在对象池中搜索可用对象,然后将其返回给客户端代码。
    如果没有可用对象,程序则创建一个新对象(并将其添加到对象池中)。
    这些代码可不少!而且它们必须位于同一处,这样才能确保重复代码不会污染程序。
    可能最显而易见,也是最方便的方式,就是将这些代码放置在我们试图重用的对象类的构造函数中。
    但是从定义上来讲,构造函数始终返回的是新对象,其无法返回现有实例。
    因此,你需要有一个既能够创建新对象,又可以重用现有对象的普通方法。这听上去和工厂方法非常相像。

简单工厂(Simple Factory)

模式介绍

简单工厂模式描述了一个类,它拥有一个包含大量条件语句的构建方法,可根据方法的参数来选择对何种产品进行初始化并将其返回。
人们通常会将简单工厂与普通的工厂或其它创建型设计模式混淆。
在绝大多数情况下,简单工厂是引入工厂方法或抽象工厂模式时的一个中间步骤。
简单工厂通常没有子类,但当从一个简单工厂中抽取出子类后,它看上去就会更像经典的工厂方法模式了。

代码示例

在下面这段代码中,我们根据配置文件的后缀(json、xml、yaml、properties),选择不同的解析器(JsonRuleConfigParser、XmlRuleConfigParser……),将存储在文件中的配置解析成内存对象 RuleConfig。

public class RuleConfigSource {
  public RuleConfig load(String ruleConfigFilePath) {
    //getFileExtension解析文件名获取扩展名
    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;
  }
}

为了让类的职责更加单一、代码更加清晰、可读性更好,我们可以将代码中涉及 parser 创建的部分逻辑剥离到一个独立的类中,让这个类只负责对象的创建。
而这个类(RuleConfigParserFactory)就是简单工厂模式,具体代码如下:

public class RuleConfigSource {
    public RuleConfig load(String ruleConfigFilePath) {
	    //getFileExtension解析文件名获取扩展名
        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;
    }
}

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

如果我们要添加新的 parser,那势必要改动到 RuleConfigParserFactory 的代码,那这是不是违反开闭原则呢?
如果不是需要频繁地添加新的 parser,只是偶尔修改一下 RuleConfigParserFactory 代码,稍微不符合开闭原则,也是完全可以接受的。
除此之外,在 RuleConfigParserFactory 的第一种代码实现中,有一组 if 分支判断逻辑,是不是应该用多态或其他设计模式来替代呢?
如果 if 分支并不是很多,代码中有 if 分支也是完全可以接受的。
应用多态或设计模式来替代 if 分支判断逻辑,也并不是没有任何缺点的,它虽然提高了代码的扩展性,更加符合开闭原则,但也增加了类的个数,牺牲了代码的可读性。

总结

尽管简单工厂模式的代码实现中,有多处 if 分支判断逻辑,违背开闭原则,但权衡扩展性和可读性,这样的代码实现在大多数情况下(比如,不需要频繁地添加 parser,也没有太多的 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 接口的 Factory 类即可。
所以,工厂方法模式比起简单工厂模式更加符合开闭原则。

重点

如果用这些工厂类来实现 RuleConfigSource 的 load() 函数。
具体的代码如下所示:

public class RuleConfigSource {
  public RuleConfig load(String ruleConfigFilePath) {
    //getFileExtension解析文件名获取扩展名
    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;
  }  
}

从上面的代码实现来看,工厂类对象的创建逻辑又耦合进了 load() 函数中,跟我们最初的代码版本非常相似,引入工厂方法非但没有解决问题,反倒让设计变得更加复杂了。

解决方案

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

public class RuleConfigSource {
  public RuleConfig load(String ruleConfigFilePath) {
    //getFileExtension解析文件名获取扩展名
	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;
  }
}

//因为工厂类只包含方法,不包含成员变量,完全可以复用,
//不需要每次都创建新的工厂类对象,所以,简单工厂模式的第二种实现思路更加合适。
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 中即可。代码的改动非常少,基本上符合开闭原则。
实际上,对于规则配置文件解析这个应用场景来说,工厂模式需要额外创建诸多 Factory 类,也会增加代码的复杂性,而且,每个 Factory 类只是做简单的 new操作,功能非常单薄(只有一行代码),也没必要设计成独立的类,所以,在这个应用场景下,简单工厂模式简单好用,比工厂方法模式更加合适。

什么时候该用工厂方法模式,而非简单工厂模式呢?

将某个代码块剥离出来,独立为函数或者类,原因是这个代码块的逻辑过于复杂,剥离之后能让代码更加清晰,更加可读、可维护。
但是,如果代码块本身并不复杂,就几行代码而已,我们完全没必要将它拆分成单独的函数或者类。
当对象的创建逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作的时候,我们推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂。
而使用简单工厂模式,将所有的创建逻辑都放到一个工厂类中,会导致这个工厂类变得很复杂。

总结

可以避免创建者和具体产品之间的紧密耦合。
单一职责原则:可以将产品创建代码放在程序的单一位置,从而使得代码更容易维护。
开闭原则:无需更改现有客户端代码,可以在程序中引入新的产品类型。
应用工厂方法模式需要引入许多新的子类,代码可能会因此变得更复杂,最好的情况是将该模式引入创建者类的现有层次结构中。

抽象工厂(Abstract Factory)

模式介绍

抽象工厂是一种创建型设计模式,它能创建一系列相关或相互依赖的对象,而无需指定其具体类。
注:抽象工厂模式和声明为 abstract 的简单工厂不同。

代码示例

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

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

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

针对这种特殊的场景,如果还是继续用工厂方法来实现的话,我们要针对每个 parser 都编写一个工厂类,也就是要编写 8 个工厂类。
如果我们未来还需要增加针对业务配置的解析器(比如 IBizConfigParser),那就要再对应地增加 4 个工厂类,而过多的类也会让系统难维护。

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

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

总结

抽象工厂模式的应用场景比较特殊。
如果代码需要与多个不同系列的相关产品交互,但是由于无法提前获取相关信息,或者出于对未来扩展性的考虑,你不希望代码基于产品的具体类进行构建,在这种情况下,你可以使用抽象工厂。
如果你有一个基于一组抽象方法的类,且其主要功能因此变得不明确,那么在这种情况下可以考虑使用抽象工厂模式。

重点回顾

工厂模式的最本质的参考标准

  • 封装变化:创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明。

  • 代码复用:创建代码抽离到独立的工厂类之后可以复用。

  • 隔离复杂性:封装复杂的创建逻辑,调用者无需了解如何创建对象。

  • 控制复杂度:将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁。

参考资料

王争-设计模式之美
https://time.geekbang.org/column/intro/100039001
Refactoring.Guru
https://refactoringguru.cn/design-patterns/factory-method
知乎
https://www.zhihu.com/question/27125796


文章作者: Baymax
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Baymax !
评论
  目录