工厂模式的分类

简单工厂

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

interface IRuleConfigParser {
  parse(configText: string): RuleConfig;
}
 
class JsonRuleConfigParser implements IRuleConfigParser {
  parse(configText: string): RuleConfig {
    // 实现解析逻辑
    return new RuleConfig();
  }
}
 
class XmlRuleConfigParser implements IRuleConfigParser {
  parse(configText: string): RuleConfig {
    // 实现解析逻辑
    return new RuleConfig();
  }
}
 
class YamlRuleConfigParser implements IRuleConfigParser {
  parse(configText: string): RuleConfig {
    // 实现解析逻辑
    return new RuleConfig();
  }
}
 
class PropertiesRuleConfigParser implements IRuleConfigParser {
  parse(configText: string): RuleConfig {
    // 实现解析逻辑
    return new RuleConfig();
  }
}
 
class RuleConfig {}
 
class InvalidRuleConfigException extends Error {
  constructor(message: string) {
    super(message);
    this.name = "InvalidRuleConfigException";
  }
}
 
class RuleConfigSource {
  load(ruleConfigFilePath: string): RuleConfig {
    const ruleConfigFileExtension = this.getFileExtension(ruleConfigFilePath);
    let parser: IRuleConfigParser | null = null;
 
    switch (ruleConfigFileExtension.toLowerCase()) {
      case "json":
        parser = new JsonRuleConfigParser();
        break;
      case "xml":
        parser = new XmlRuleConfigParser();
        break;
      case "yaml":
        parser = new YamlRuleConfigParser();
        break;
      case "properties":
        parser = new PropertiesRuleConfigParser();
        break;
      default:
        throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);
    }
 
    const configText = ""; // 假设从ruleConfigFilePath文件中读取配置文本到configText中
    return parser.parse(configText);
  }
 
  private getFileExtension(filePath: string): string {
    // 解析文件名获取扩展名,比如rule.json,返回json
    return filePath.slice(((filePath.lastIndexOf(".") - 1) >>> 0) + 2);
  }
}

在这个函数中,有创建 parser 和 解析两个部分,为了当代码逻辑更加清晰,可读性更好,我们要善于将功能独立的代码块封装成函数。在这里,我们把代码中涉及到 parser 创建的部分逻辑抽离出来,抽象成 createParser() 函数。重构后的代码如下所示:

interface IRuleConfigParser {
  parse(configText: string): RuleConfig;
}
 
class JsonRuleConfigParser implements IRuleConfigParser {
  parse(configText: string): RuleConfig {
    // 实现JSON解析逻辑
    return new RuleConfig();
  }
}
 
class XmlRuleConfigParser implements IRuleConfigParser {
  parse(configText: string): RuleConfig {
    // 实现XML解析逻辑
    return new RuleConfig();
  }
}
 
class YamlRuleConfigParser implements IRuleConfigParser {
  parse(configText: string): RuleConfig {
    // 实现YAML解析逻辑
    return new RuleConfig();
  }
}
 
class PropertiesRuleConfigParser implements IRuleConfigParser {
  parse(configText: string): RuleConfig {
    // 实现Properties解析逻辑
    return new RuleConfig();
  }
}
 
class RuleConfig {}
 
class InvalidRuleConfigException extends Error {
  constructor(message: string) {
    super(message);
    this.name = "InvalidRuleConfigException";
  }
}
 
class RuleConfigSource {
  load(ruleConfigFilePath: string): RuleConfig {
    const ruleConfigFileExtension = this.getFileExtension(ruleConfigFilePath);
    const parser = this.createParser(ruleConfigFileExtension);
 
    const configText = ""; // 假设从ruleConfigFilePath文件中读取配置文本到configText中
    return parser.parse(configText);
  }
 
  private getFileExtension(filePath: string): string {
    // 解析文件名获取扩展名,比如rule.json,返回json
    return filePath.slice(((filePath.lastIndexOf(".") - 1) >>> 0) + 2);
  }
 
  private createParser(fileExtension: string): IRuleConfigParser {
    switch (fileExtension.toLowerCase()) {
      case "json":
        return new JsonRuleConfigParser();
      case "xml":
        return new XmlRuleConfigParser();
      case "yaml":
        return new YamlRuleConfigParser();
      case "properties":
        return new PropertiesRuleConfigParser();
      default:
        throw new InvalidRuleConfigException("Rule config file format is not supported: " + fileExtension);
    }
  }
}

为了让类更符合设计原则:单一职责(SRP),我们可以把 createParser()函数剥离到一个独立的类中,让这个类只负责对象的创建。具体的代码如下所示:

// IRuleConfigParser 接口和各种解析器实现保持不变
interface IRuleConfigParser {
  parse(configText: string): RuleConfig;
}
 
class JsonRuleConfigParser implements IRuleConfigParser {
  parse(configText: string): RuleConfig {
    return new RuleConfig();
  }
}
 
class XmlRuleConfigParser implements IRuleConfigParser {
  parse(configText: string): RuleConfig {
    return new RuleConfig();
  }
}
 
class YamlRuleConfigParser implements IRuleConfigParser {
  parse(configText: string): RuleConfig {
    return new RuleConfig();
  }
}
 
class PropertiesRuleConfigParser implements IRuleConfigParser {
  parse(configText: string): RuleConfig {
    return new RuleConfig();
  }
}
 
class RuleConfig {}
 
class InvalidRuleConfigException extends Error {
  constructor(message: string) {
    super(message);
    this.name = "InvalidRuleConfigException";
  }
}
 
// RuleConfigParserFactory 类
class RuleConfigParserFactory {
  static createParser(fileExtension: string): IRuleConfigParser {
    switch (fileExtension.toLowerCase()) {
      case "json":
        return new JsonRuleConfigParser();
      case "xml":
        return new XmlRuleConfigParser();
      case "yaml":
        return new YamlRuleConfigParser();
      case "properties":
        return new PropertiesRuleConfigParser();
      default:
        throw new InvalidRuleConfigException("Rule config file format is not supported: " + fileExtension);
    }
  }
}
 
// RuleConfigSource 类使用 RuleConfigParserFactory 来创建解析器
class RuleConfigSource {
  load(ruleConfigFilePath: string): RuleConfig {
    const ruleConfigFileExtension = this.getFileExtension(ruleConfigFilePath);
    const parser = RuleConfigParserFactory.createParser(ruleConfigFileExtension);
 
    const configText = ""; // 假设从 ruleConfigFilePath 文件中读取配置文本到 configText 中
    return parser.parse(configText);
  }
 
  private getFileExtension(filePath: string): string {
    return filePath.slice(((filePath.lastIndexOf(".") - 1) >>> 0) + 2);
  }
}

如果 parser 可以复用,可以借助单例模式的特点,减少不必要的对象创建和内存消耗,代码如下:

class RuleConfigParserFactory {
  private static instance: RuleConfigParserFactory;
 
  // 私有化构造函数,防止外部使用new操作符创建实例
  private constructor() {}
 
  // 提供一个全局访问点用以获取实例,并确保只有一个实例被创建
  public static getInstance(): RuleConfigParserFactory {
    if (!RuleConfigParserFactory.instance) {
      RuleConfigParserFactory.instance = new RuleConfigParserFactory();
    }
    return RuleConfigParserFactory.instance;
  }
 
  public createParser(fileExtension: string): IRuleConfigParser {
    switch (fileExtension.toLowerCase()) {
      case "json":
        return new JsonRuleConfigParser();
      case "xml":
        return new XmlRuleConfigParser();
      case "yaml":
        return new YamlRuleConfigParser();
      case "properties":
        return new PropertiesRuleConfigParser();
      default:
        throw new InvalidRuleConfigException("Rule config file format is not supported: " + fileExtension);
    }
  }
}
 
class RuleConfigSource {
  load(ruleConfigFilePath: string): RuleConfig {
    const ruleConfigFileExtension = this.getFileExtension(ruleConfigFilePath);
    // 使用单例获取 RuleConfigParserFactory 实例
    const factory = RuleConfigParserFactory.getInstance();
    const parser = factory.createParser(ruleConfigFileExtension);
 
    const configText = ""; // 假设从 ruleConfigFilePath 文件中读取配置文本到 configText 中
    return parser.parse(configText);
  }
 
  private getFileExtension(filePath: string): string {
    return filePath.slice(((filePath.lastIndexOf(".") - 1) >>> 0) + 2);
  }
}

这里的业务场景下,我们要添加 parser,肯定会改到 RuleConfigParserFactory的代码,在简单工厂模式的代码中,有多处 if 分支判断逻辑,违背设计原则:开闭原则(OCP),但权衡拓展性和可读性,这样的代码实现在大多数情况下(比如,不需要频繁地添加 parser,也没有太多的 parser)是没有问题的。

工厂方法

如果我们非要把 switch 分支逻辑去掉,那该怎么办呢?很简单,利用多态(Polymorphism)的实现思路,对上面的代码进行重构。

interface IRuleConfigParser {
  parse(configText: string): RuleConfig;
}
 
class JsonRuleConfigParser implements IRuleConfigParser {
  parse(configText: string): RuleConfig {
    // 解析JSON配置
    return new RuleConfig();
  }
}
 
class XmlRuleConfigParser implements IRuleConfigParser {
  parse(configText: string): RuleConfig {
    // 解析XML配置
    return new RuleConfig();
  }
}
 
class YamlRuleConfigParser implements IRuleConfigParser {
  parse(configText: string): RuleConfig {
    // 解析YAML配置
    return new RuleConfig();
  }
}
 
class PropertiesRuleConfigParser implements IRuleConfigParser {
  parse(configText: string): RuleConfig {
    // 解析Properties配置
    return new RuleConfig();
  }
}
 
class RuleConfig {
  // 规则配置的具体内容
}
 
class InvalidRuleConfigException extends Error {
  constructor(message: string) {
    super(message);
    this.name = "InvalidRuleConfigException";
  }
}
 
class RuleConfigParserFactory {
  private static parsers = new Map<string, { new(): IRuleConfigParser }>();
 
  private constructor() {}
 
  public static registerParser(extension: string, parserType: { new(): IRuleConfigParser }) {
    RuleConfigParserFactory.parsers.set(extension.toLowerCase(), parserType);
  }
 
  public static createParser(fileExtension: string): IRuleConfigParser | null {
    const parserConstructor = RuleConfigParserFactory.parsers.get(fileExtension.toLowerCase());
    if (!parserConstructor) {
      throw new InvalidRuleConfigException("Rule config file format is not supported: " + fileExtension);
    }
    return new parserConstructor();
  }
}
 
// 在应用启动时注册所有解析器
function registerParsers() {
  RuleConfigParserFactory.registerParser("json", JsonRuleConfigParser);
  RuleConfigParserFactory.registerParser("xml", XmlRuleConfigParser);
  RuleConfigParserFactory.registerParser("yaml", YamlRuleConfigParser);
  RuleConfigParserFactory.registerParser("properties", PropertiesRuleConfigParser);
}
 
registerParsers();
 
class RuleConfigSource {
  load(ruleConfigFilePath: string): RuleConfig {
    const ruleConfigFileExtension = this.getFileExtension(ruleConfigFilePath);
    const parser = RuleConfigParserFactory.createParser(ruleConfigFileExtension);
    if (!parser) {
      throw new Error(`Parser for extension ${ruleConfigFileExtension} not found.`);
    }
    const configText = this.readConfigFile(ruleConfigFilePath); // 假设实现了这个方法
    return parser.parse(configText);
  }
 
  private getFileExtension(filePath: string): string {
    // 实现根据文件路径获取扩展名的逻辑
    return filePath.slice(((filePath.lastIndexOf(".") - 1) >>> 0) + 2);
  }
 
  private readConfigFile(filePath: string): string {
    // 实现读取文件内容的逻辑,这里仅为示意,实际应根据环境具体实现
    return "";
  }
}

当我们需要添加新的规则配置解析器的时候,我们只需要创建新的 parser 并使用 factory 注册。代码改动非常少,基本上符合设计原则:开闭原则(OCP)

借助工厂模式实现 简易 DI 容器

DI 容器相当于一个大的工厂类,负责在程序启动的时候,根据配置(要创建哪些类对象,每个类对象的创建需要依赖哪些其他类对象)事先创建好对象。当应用程序需要使用某个类对象的时候,直接从容器中获取即可。正是因为它持有一堆对象,所以这个框架才被称为“容器”。