主流的工厂模式有两种分类方式。第一种分类方法是将工厂模式分为3个小类:简单工厂模式、工厂方法模式、抽象工厂模式。第二种分类方法(GOF合著的《设计模式:可复用面向对象软件基础》一书中使用的分类方法)将工厂模式分为工厂方法和抽象工厂模式,把简单工厂模式看成是工厂方法模式的一种特列。这里按照第一种分类方式。
简单工厂模式---Simple Factory Pattern
假设我们现在有这样的一个需求,需要根据配置文件的扩展名(如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))
parse = new PropertiesRuleConfigParser();
else
throw new InvalidRuleConfigException("Rule config file format is not supported:" + ruleConfigFilePath);
String configText = "";
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(string filePath){
//解析文件的扩展名
return "json";
}
}
这段代码咋一看其实还行,但是想想为了让代码的逻辑更加的清晰、可读性更加的高,我们可以将功能独立的代码块封装成函数。因此我们可以将上述代码中设计parser创建对象的部分剥离出来,封装成createParser()函数。重构之后的代码如下;
public class RuleConfigSource{
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 = "";
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(string filePath){
//解析文件的扩展名
return "json";
}
private IRuleConfigParser createParser(String ruleConfigFileExtension){
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))
parse = new PropertiesRuleConfigParser();
return parse;
}
}
为了让类的职责单一、代码清晰,我们还可以进一步将createParser()函数从RuleConfigSource类中剥离出来,并将其放到一个独立的类中,让这个类仅仅负责对象的创建。这个类就是我们即将介绍的简单工厂模式的工厂类。进一步重构之后的代码如下:
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 = "";
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(string filePath){
//解析文件的扩展名
return "json";
}
}
public class RuleConfigParserFactory{
public static IRuleConfigParser createParser(String ruleConfigFileExtension){
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))
parse = new PropertiesRuleConfigParser();
return parse;
}
}
重构到这种方式,就可以说是简单工厂模式的一种实现方式了。大部分的工厂的命名都是以“Factory”结尾,但是这并不是必须的,在java中还有一些其他的方式比如DateFormat、Calender等他们也是工厂类。对于工厂类的命名没有强制的要求,一般是根据具体的需求来确定的。
在这种工厂模式中,我们每次调用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 ruleConfigFileExtension){
if(ruleCongfigFileExtension == null || ruleConfigFileExtension.isEmpty())
return null;
return cachedParsers.get(ruleConfigFileExtension.toLowerCase());
}
}
这种代码实际上还是不是那么的完美,想想当我们每次添加新的parser,那么就需要改动工厂类的代码,这就违反了开闭原则。但是要是并没有频繁的修改工厂类这种设计其实也是可以的,就是这个工厂类不符合开闭原则也是可以接受的。
此外工厂类的第一种实现中有一组if分支判断逻辑语句,是不是还可以将其替换成其他的设计模式或者是替换成多态呢?
工厂方法模式---Factory Method Pattern
接着上面的问题,我们如果需要将if分支判断逻辑去掉,经典的解决方法就是利用多态替换if分支判断逻辑。再次重构之后的代码如下:
public interface RuleConfigParserFactory{
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接口的工厂类就行了。因此工厂方法模式比简单工厂模式更加的符合开闭原则。
但是上面的代码还是存在着一些问题。当我们在使用的时候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 = "";
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(string filePath){
//解析文件的扩展名
return "json";
}
}
从上面的代码来看,尽管parser对象的创建逻辑从RuleConfigSource类中剥离了,但是工厂类对象的创建逻辑有和RuleConfigSource类耦合了,也就是说引入了工厂方法模式,不但没有解决问题,反而让设计变得更加的复杂了。
为了解决上面出现的问题,我们可以为工厂类再创建一个简单工厂,也就是工厂的工厂,用来去创建工厂类的对象。重构之后的代码如下:
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 = "";
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(string filePath){
//解析文件的扩展名
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对应的类和工厂类,并且在RuleConfigParserFactoryMap类中,将工厂对象添加到cachedFactories中就行了。这样代码改动的也比较少,基本上符合开闭原则。
抽象工厂模式---Abstract Factory Pattern
在简单工厂模式和工厂方法模式中,类只有一种分类方式。比如在我们上面提到的例子中,解析器只会根据配置文件的格式(JSON,XML,YAML等)来分类。但是,如果解析器有两种分类方式,既可以按照配置文件格式来分类,又可以按照解析的对象(规则配置或者系统配置)来分类,那么通过组合,就会得到8中解析器类。
(1)针对规则配置的解析器(基于接口IRuleConfigParser的实现类)
- JsonRuleConfigParser
- XmlRuleConfigParser
- YamlRuleConfigParser
- PropertiesRuleConfigParser
(2)针对系统配置的解析器(基于接口ISystemConfigParser的实现类)
- JsonSystemConfigParser
- XmlSystemConfigParser
- YamlSystemConfigParser
- PropertiesSystemConfigParser
针对这种特殊场景,使用工厂方法模式来实现的话,那么对于每个parser分别写一个工厂类,就要写8个工厂类,如果未来还要增加配置的解析器,那么又要增加工厂类,然而过多的工厂类会让系统变得难以维护。对于这种问题抽象工厂模式就诞生了。
我们让一个工厂负责创建多种不同类型的parser对象,而不是就创建一种类型的parser对象。这样就可以有效地减少工厂类地个数。实例代码如下:
public interface IConfigParserFactory{
IRuleConfigParser createRuleParser();
ISystemConfigParser createSystemParser();
}
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();
}
}
public class YamlConfigParserFactory implements IConfigParserFactory{
@override
public IRuleConfigParser createRuleParser(){
return new YamlRuleConfigParser();
}
@override
public ISystemConfigParser createSystemParser(){
return new YamlSystemConfigParser();
}
}
public class PropertiesConfigParserFactory implements IConfigParserFactory{
@override
public IRuleConfigParser createRuleParser(){
return new PropertiesRuleConfigParser();
}
@override
public ISystemConfigParser createSystemParser(){
return new PropertiesSystemConfigParser();
}
}
工厂模式地应用场景总结
当对象地创建逻辑比较复杂地时候可以考虑工厂模式,即封装对象地创建过程,将对象地创建和使用分离,来降低代码地复杂度。但是任何去判断呢?主要是以下两种情况。
第一:类似规则配置解析地例子,代码种存在着if分支判断逻辑,其根据不同地类型动态地创建不同地对象。这种情况可以根据实际情况选择是简单工厂模式或者是工厂方法模式。
第二:尽管不用根据不同地类型创建不同的对象,但是,单个对象的创建比较复杂,如需要组合其他的类的对象,并进行各种复杂的初始化操作。
最后总结一下,工厂模式的作用有下列4个,也是判断是否是使用工厂模式的参考标准。
- 封装变化:封装创建逻辑,创建逻辑的变更对调用者透明
- 代码复用
- 隔离复杂性:封装复杂的创建逻辑
- 空值复杂度:分离创建逻辑和使用逻辑