工厂方法模式(Factory Method) 是一种创建型设计模式, 它在父类中提供一个创建对象的方法, 允许自雷决定实例化对象的类型.
工厂方法分类
工厂方法细分可分为简单工厂, 工厂方法和抽象方法, 不过, 在 Gof 的 设计模式书中, 他将简单工厂看作是工厂方法模式的一种特例, 所以工厂模式只被分成了工厂方法和抽象方法两类. 其实, 前面一种分法比较常见, 所以, 我们用第一种分法.
那么什么时候该用工厂模式? 相比较直接 new 来创建对象, 用工厂模式来创建究竟有什么好处呢?
简单工厂
我们通过一个例子来解释下什么是简单工厂模式.
下面的这段代码, 我们根据配置文件的后缀(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";
}
}
为了让代码逻辑更加清晰, 可读性更好, 我们要把功能独立的代码封装成函数. 按照这个思路, 我们可以将代码中涉及到的 parser 创建的部分逻辑剥离出来, 抽象成 createParser() 函数. 重构之后的代码如下所示:
public RuleConfig load(String ruleConfigFilePath) {
String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
IRuleConfigParser parser = 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";
}
private 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;
}
}
为了让类的职责更加单一, 代码更加清晰, 我们可以进一步将 createParser() 函数剥离到一个独立的类中, 让这个类只负责对象的创建. 而这个类就是我们现在说的简单工厂模式类. 具体的代码如下所示:
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, Calender. 除此之外, 工厂类中创建对象的方法一般都是 create 开头, 比如代码中的 createParser(), 但有的也命名为 getInstance(), createInstance(), newInstance()等等, 我们根据具体的场景和习惯来命名就好.
在上面的代码实现中, 我们每次调用 RuleConfigParserFactory() 的 createParser() 的时候, 都要创建一个新的 parser. 实际上, 如果 parser 可以复用, 为了节省内存和对象的时间, 我们可以将parser 事先创建好缓存起来. 当调用 createParser() 函数的时候, 我们从缓存中取出 parser 对象直接使用.
这个有点类似于单例模式和简单工厂模式的结合, 具体代码如下所示. 我们把上一种实现方式叫做简单工厂模式的第一种实现, 把下面这种实现方式叫做简单工厂模式的第二种实现方式.
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;
}
}
对于上面两种简单工厂模式的实现方法, 如果我们要添加新的parser, 那么要改动 RuleConfigParserFactory 的代码, 那这是不是违反了开闭原则呢? 其实, 如果不是需要频繁添加新的 parser, 只是偶尔修改下 RuleCOnfigParserFactory 代码, 稍微不符合开闭原则, 也是可以接受的.
此外, 在 RuleCOnfigParserFactory 的第一种代码实现中, 有一组 if 分支判断逻辑, 是不是应该用多态或者其他设计模式来替代呢? 其实, 如果 if 分支并不是很多, 代码中有 if 分支也是可以接受的. 应用多态或设计模式来替代 if 分支判断逻辑, 也不是没有任何缺点的, 它虽然提高了代码的扩展性, 更加符合开闭原则, 但也增加了类的个数, 牺牲了代码的可独性.
总之, 尽管简单工厂模式的代码实现中, 有多处 if 分支判断逻辑, 违背了开闭原则, 但是权衡扩展性和可读性, 这样的代码实现在大多数情况下(比如, 不需要频繁的添加 parser, 也没有太多的 parser) 是没有问题的.
工厂方法
如果我们一定要将 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) {
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 中即可. 代码的改动非常少,基本上符合开闭原则.
实际上,对于规则配置文件解析这个应用场景来说,工厂模式需要额外创建诸多 Factory 类,也会增加代码的复杂性,而且,每个 Factory 类只是做简单的 new 操作,功能非常单薄(只有一行代码),也没必要设计成独立的类,所以,在这个应用场景下,简单工厂模式简单好用,比工厂方法模式更加合适.
那什么时候该用工厂方法模式,而非简单工厂模式呢?
我们前面提到,之所以将某个代码块剥离出来,独立为函数或者类,原因是这个代码块的逻辑过于复杂,剥离之后能让代码更加清晰,更加可读、可维护. 但是,如果代码块本身并不复杂,就几行代码而已,我们完全没必要将它拆分成单独的函数或者类.
基于这个设计思想,当对象的创建逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作的时候,我们推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂. 而使用简单工厂模式,将所有的创建逻辑都放到一个工厂类中,会导致这个工厂类变得很复杂.
除此之外,在某些场景下,如果对象不可复用,那工厂类每次都要返回不同的对象. 如果我们使用简单工厂模式来实现,就只能选择第一种包含 if 分支逻辑的实现方式. 如果我们还想避免烦人的 if-else 分支逻辑,这个时候,我们就推荐使用工厂方法模式.
抽象工厂(Abstract Factory)
在简单工厂和工厂方法中,类只有一种分类方式. 比如,在规则配置解析那个例子中,解析器类只会根据配置文件格式(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代码