当前位置:网站首页>彻底理解工厂模式

彻底理解工厂模式

2022-08-09 10:46:00 GeorgiaStar

一般情况下,工厂模式分为三种更加细分的类型:简单工厂、工厂方法和抽象工厂。不过,在GoF的《设计模式》一书中,它将简单工厂模式看作是工厂方法模式的一种特例,所以工厂模式只被分成了工厂方法和抽象工厂两类。实际上,前面一种分类方法更加常见。在这三种细分的工厂模式中,简单工厂、工厂方法原理比较简单,在实际的项目中也比较常用。而抽象工厂的原理稍微复杂点,在实际的项目中相对也不常用。

简单工厂(Simple Factory)

简单工厂模式:定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。因为在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为静态工厂方法模式,它属于创建型模式。简单工厂模式的结构如图所示:

在简单工厂模式中包含如下几个角色:

  • Factory(工厂角色):工厂角色即工厂类,它是简单工厂模式的核心,负责实现创建所有产品实例的内部逻辑;工厂类可以被外界直接调用,创建所需的产品对象;在工厂类中提供了静态的工厂方法,它的返回类型为抽象产品类型Product。
  • Product(抽象产品角色):它是工厂类所创建的所有对象的父类,封装了各种产品对象的公有方法,它的引入将提高系统的灵活性,使得在工厂类中只需定义一个通用的工厂方法,因为所有创建的具体产品对象都是其子类对象。
  • ConcreteProduct(具体产品角色):它是简单工厂模式的创建目标,所有被创建的对象都充当这个角色的某个具体类的实例。每一个具体产品角色都继承了抽象产品角色,需要实现在抽象产品中声明的抽象方法。

通过一个例子来解释,假设有这样一个需求我们根据配置文件的后缀(json、xml、yaml、properties),选择不同的文件解析器(JsonRuleConfigParser、XmlRuleConfigParser等),将存储在文件中的字符解析成内存对象RuleConfig。最简单的代码实现如下:

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

以上实现不够优雅,为了让类的职责更加单一、代码结构更加清晰、可读性更好,我们还可以进一步将创建解析器的逻辑剥离到一个独立的类中,让这个类只负责对象的创建,而这个类就是简单工厂类。具体的代码如下所示:

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

大部分工厂类都是以Factory这个单词结尾的,但也不是必须的,比如 Java中的DateFormat、Calendar。除此之外,工厂类中创建对象的方法一般都是create开头,比如代码中的createParser(),但有的也命名为 getInstance()、createInstance()、newInstance(),有的甚至命名为 valueOf(),例如Java String类的valueOf()函数。

在上面的代码实现中,我们每次调用RuleConfigParserFactory的 createParser()的时候,都要创建一个新的parser对象实例。实际上,如果parser不记录状态是可以复用的,为了节省内存和对象创建的时间,我们可以将parser设计成单例,事先创建好并缓存起来。当调用createParser()函数的时候,我们从缓存中直接取出parser对象。基于这个思想,RuleConfigParserFactory类改写如下:

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 (StringUtils.isBlank(configFormat) {
    
      throw new IllegalArgumentException("Rule config file format is blank");
    }
    IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase());
    return parser;
  }
}

简单工厂模式简单直接,但如果我们要添加新的parser,那势必要改动到RuleConfigParserFactory的代码,这样就违反了开闭原则,对parser的扩展不利。简单工厂模式适用于工厂类负责创建的对象比较少的场景,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂

工厂方法(Factory Method)

简单工厂模式中只提供一个工厂类,该工厂类处于对产品类进行实例化的中心位置,它需要知道每一种产品的创建细节,并决定何时实例化哪一个产品类。所有的产品都由同一个工厂创建,工厂类职责较重,业务逻辑较为复杂,具体产品与工厂类之间的耦合度高,影响了系统的灵活性和扩展性,而工厂方法模式则可以很好地解决这一问题。

在工厂方法模式中,不再使用一个统一的工厂类来创建所有类型的产品,而是针对不同的产品提供不同的工厂。工厂方法模式结构如图所示:

在工厂方法模式中包含如下几个角色:

  • Product(抽象产品):它是定义产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的公共父类。
  • ConcreteProduct(具体产品):它实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应
  • Factory(抽象工厂):在抽象工厂类中,声明了工厂方法(createProduct),用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。
  • ConcreteFactory(具体工厂):它是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户端调用,返回一个具体产品类的实例。

与简单工厂模式相比,工厂方法模式最重要的区别是引入了抽象工厂角色,抽象工厂可以是接口,也可以是抽象类或者具体类。承接上面的例子,使用工厂方法模式来重构,其典型代码如下所示:

//抽象工厂接口,生成IRuleConfigParser
public interface IRuleConfigParserFactory {
    
  IRuleConfigParser createParser();
}
//JsonRuleConfigParser工厂,专用于生成JsonRuleConfigParser
public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory {
    
  @Override
  public IRuleConfigParser createParser() {
    
    return new JsonRuleConfigParser();
  }
}

//XmlRuleConfigParser工厂,专用于生成XmlRuleConfigParser
public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory {
    
  @Override
  public IRuleConfigParser createParser() {
    
    return new XmlRuleConfigParser();
  }
}

//YamlRuleConfigParser工厂,专用于生成YamlRuleConfigParser
public class YamlRuleConfigParserFactory implements IRuleConfigParserFactory {
    
  @Override
  public IRuleConfigParser createParser() {
    
    return new YamlRuleConfigParser();
  }
}

//PropertiesRuleConfigParser工厂,专用于生成PropertiesRuleConfigParser
public class PropertiesRuleConfigParserFactory implements IRuleConfigParserFactory {
    
  @Override
  public IRuleConfigParser createParser() {
    
    return new PropertiesRuleConfigParser();
  }
}

这就是工厂方法模式的典型代码实现。这样当我们新增一种parser的时候,只需要新增一个实现了IRuleConfigParserFactory接口的Factory类即可。所以,工厂方法模式比起简单工厂模式更加符合开闭原则。

从上面的工厂方法的实现来看,一切都很完美,但是工厂类对象的创建逻辑又耦合进了RuleConfigSource类的load()函数中,要通过多次if/esle去找到该用哪个工厂来生成解析器。

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);
    }
    //通过工厂类生成具体的parser
    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";
  }

怎么解决工厂类对象的创建复杂这个问题呢?工厂模式恰恰专门用于将对象创建与使用解耦的设计模式。我们可以为工厂类再创建一个简单工厂,也就是工厂的工厂,用来创建工厂类对象。重构代码如下,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;
  }
}

工厂方法模式继承了简单工厂模式的优点,同时还弥补了简单工厂模式的不足。工厂方法模式是使用频率最高的设计模式之一,是很多开源框架和API类库的核心模式

抽象工厂(Abstract Factory)

抽象工厂模式为创建一组对象提供了一种解决方案。与工厂方法模式相比,抽象工厂模式中的具体工厂不只是创建一种产品,它负责创建一族产品

产品族: 位于不同产品等级结构中,功能相关联的产品组成的家族。

在抽象工厂模式中,每一个具体工厂都提供了多个工厂方法用于产生多种不同类型的产品,这些产品构成了一个产品族,抽象工厂模式结构如下图所示:

抽象工厂中包含的角色:

  • AbstractFactory(抽象工厂):它声明了一组用于创建一族产品的方法,每一个方法对应一种产品。
  • ConcreteFactory(具体工厂):它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中。
  • AbstractProduct(抽象产品):它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。
  • ConcreteProduct(具体产品):它定义具体工厂生产的具体产品对象,实现抽象产品接口中声明的业务方法。

举个简单的例子解释一下。

//定义抽象的图形
public interface Chart {
    
  void draw();
}
//定义抽象的图形解析器
public interface ChartParser {
    
  void parse(Chart chart);
}

//图形及图形解析器构成产品簇
public interface ChartParserFactory {
    
  Chart createChart();
  ChartParser createChartParser();
  //此处可以扩展
}

public class HistogramFactory implements ChartParserFactory {
    
  @Override
  public Chart createChart() {
    
    return new Histogram();
  }

  @Override
  public ChartParser createChartParser() {
    
    return new HistogramParser();
  }
}

public class LineFactory implements ChartParserFactory {
    
  @Override
  public Chart createChart() {
    
    return new Lie();
  }

  @Override
  public ChartParser createChartParser() {
    
    return new LineParser();
  }
}

抽象工厂模式的应用场景比较特殊,所以很少用到。不如简单工厂模式与工厂方法模式常用。

使用工厂方法模式足以应付我们在现实场景中可能遇到的大部分业务需求。但是当产品种类非常多时,就会出现大量的与之对应的工厂类,这不是我们所希望的,所以在这种产品种类及其多的情况下应该使用简单工厂模式与工厂方法模式相结合的方式来减少工厂类:对于产品树上类似的种类(一般是树的叶子中互为兄弟的)使用简单工厂模式来实现。对于产品树上的产品族,可以使用抽象工厂模式

总结

当创建逻辑比较复杂的时候,我们就考虑使用工厂模式,封装对象的创建过程,将对象的创建和使用相分离。何为创建逻辑比较复杂呢?主要有两种情况。

  • 第一种情况:类似规则配置解析的例子,代码中存在if-else分支判断,动态地根据不同的类型创建不同的对象。针对这种情况,我们就考虑使用工厂模式,将if-else这些复杂的创建对象的代码抽离出来,放到工厂类中。
  • 第二种情况,尽管我们不需要根据不同的类型创建不同的对象,但是,单个对象本身的创建过程比较复杂,比如前面提到的要组合其他类对象,做各种初始化操作。在这种情况下,我们也可以考虑使用工厂模式,将对象的创建过程封装到工厂类中。

当每个对象的创建逻辑都比较简单的时候,推荐使用简单工厂模式,将多个对象的创建逻辑放到一个工厂类中。当每个对象的创建逻辑都比较复杂的时候,为了避免设计一个过于庞大的简单工厂类,推荐使用工厂方法模式,将创建逻辑拆分得更细,每个对象的创建逻辑独立到各自的工厂类中。

原网站

版权声明
本文为[GeorgiaStar]所创,转载请带上原文链接,感谢
https://blog.csdn.net/fuzhongmin05/article/details/109220481