侧边栏壁纸
博主头像
qingtian博主等级

喜欢是一件细水流长的事,是永不疲惫的双向奔赴~!

  • 累计撰写 94 篇文章
  • 累计创建 42 个标签
  • 累计收到 1 条评论

再谈工厂模式

qingtian
2022-03-28 / 0 评论 / 0 点赞 / 182 阅读 / 6,382 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-03-28,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

工厂模式

工厂模式的种类

工厂模式可以进一步被细分为简单工厂模式工厂方法模式抽象工厂模式

简单工厂(Simple Factory)

下面是一个简单工厂模式的示例:我们通过不同文件的后缀名创建出不同的解析器,来解析文件中的配置信息。

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 YamlJsonConfigParser();  
		 }else if ("properties".equalsIgnoreCase(configFormat)) {  
		            parser = new PropertiesJsonConfigParser();  
		 }  
			    return parser;  
		 }  
}

---------------------------------------------------------------------------------

public class RuleConfigSource {  
    public RuleConfig load(String ruleConfigFilePath) {  
        String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);  
		IRuleConfigParser parser = RuleConfigParserFactory.createParser(ruleConfigFileExtension);  
		if (null == parser) {  
			            throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);  
	 }  
  
         String configText = "";  
		 //从文件中读取配置信息到configText中  
		 RuleConfig ruleConfig = parser.parse(configText);  
		 return ruleConfig;  
	 }  
  
    /**  
	 * 获取文件后缀名  
	 * @param ruleConfigFilePath  
	 * @return  
	 */  
	 private String getFileExtension(String ruleConfigFilePath) {  
         //解析设备获取拓展名,比如rule.json返回json  
		 return "json";  
	 }  
}

根据上面的代码实现中,我们每次调用RuleConfigParserFactorycreateParser方法都会创建一个新的parser。实际上,如果parser可以复用,为了节省内存和创建对象的消耗,可以将创建好的parser缓存起来,调用createParser()方法时,我们可以从缓存中取出直接使用。

具体代码实现如下:

public class RuleConfigParserFactory {  
  
    private static final Map<String,IRuleConfigParser> cachedParsers = new HashMap<>();  
  
 static {  
         cachedParsers.put("json",new JsonRuleConfigParser());  
		 cachedParsers.put("xml",new XmlRuleConfigParser());  
		 cachedParsers.put("yaml",new YamlJsonConfigParser());  
		 cachedParsers.put("properties",new PropertiesJsonConfigParser());  
 }  
  
    public static IRuleConfigParser createParser(String configFormat) {  
  
        if (StringUtils.isEmpty(configFormat)) {  
            return null;  
		 }  
        IRuleConfigParser parser = cachedParsers.get(configFormat);  
		return parser;  
	 }  
}

上面这种使用方式很像单例模式和工厂模式的结合。

简单工厂模式的问题是违反了开闭原则,如果我们需要修改或者新增新的parser就要对RuleConfigParserFactory进行修改。

工厂方法模式(Factory Method)

public interface IRuleConfigParserFactory {  
    IRuleConfigParser createParser();  
}
----------------------------------------------------
public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory{  
     @Override  
	 public IRuleConfigParser createParser() {  
	        return new JsonRuleConfigParser();  
	 }  
}

public class PropertiesRuleConfigParserFactory implements IRuleConfigParserFactory{  
     @Override  
	 public IRuleConfigParser createParser() {  
	        return new PropertiesJsonConfigParser();  
	 }  
}

public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory{  
     @Override  
	 public IRuleConfigParser createParser() {  
	        return new XmlRuleConfigParser();  
	 }  
}

public class YamlRuleConfigParserFactory implements IRuleConfigParserFactory{  
     @Override  
	 public IRuleConfigParser createParser() {  
	        return new YamlJsonConfigParser();  
	 }  
}

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

我们可以为这些工厂类在创建一个简单工厂,也就是工厂的工厂,用来创建工厂对象。这主要是用来管理工厂类。
RuleConfigParserFactoryMap 类是创建工厂对象的工厂类,getParserFactory() 返回的是缓存好的单例工厂对象。

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 (StringUtils.isEmpty(type)) {  
            return null;  
	 }  
	    IRuleConfigParserFactory parserFactory = cachedFactories.get(type.toLowerCase());  
		 return parserFactory;  
	 }  
}

--------------------------------------------------------------
public class RuleConfigSource {  
    public RuleConfig load(String ruleConfigFilePath) {  
         String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);  
		 IRuleConfigParserFactory parserFactory = RuleConfigParserFactoryMap.getParserFactory(ruleConfigFileExtension);  
		 if (null == parserFactory) {  
		            throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);  
		 }  
        IRuleConfigParser parser = parserFactory.createParser();  
		String configText = "";  
		//从文件中读取配置信息到configText中  
		RuleConfig ruleConfig = parser.parse(configText);  
		return ruleConfig;  
	 }  
  
    /**  
	 * 获取文件后缀名  
	 * @param ruleConfigFilePath  
	 * @return  
	 */  
	 private String getFileExtension(String ruleConfigFilePath) {  
         //解析设备获取拓展名,比如rule.json返回json  
		 return "json";  
	 }  
}

当我们需要添加新的规则配置解析器的时候,我们只需要创建新的 parser 类和 parserfactory 类,并且在 RuleConfigParserFactoryMap 类中,将新的 parser factory 对象添加到cachedFactories 中即可。代码的改动非常少,基本上符合开闭原则。

抽象工厂(Abstract Factory)

抽象工厂模式是根据工厂生产的产品族来进行划分的。

比如说,我们不仅拥有根据配置文件种类还有根据系统配置来生成配置类。
那么我们就会有下面8个解析器类。

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

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

如果为每一个parser类都编写一个工厂,那么需要编写8个工厂类。
如果我们未来还需要增加针对业务配置的解析器(比如 IBizConfigParser),那就要再对应地增加 4 个工厂类。
不断增加的过多的类会使系统变得难以维护。
抽象工厂就是针对这种非常特殊的场景而诞生的。我们可以让一个工厂负责创建多个不同类型的对象(IRuleConfigParser、ISystemConfigParser 等),而不是只创建一种 parser对象。这样就可以有效地减少工厂类的个数。

public interface IRuleConfigParserFactory {  
     IRuleConfigParser createRuleParser();  
	 ISystemConfigParser createSysParser();  
}
-------------------------------------------------------------
public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory{  
     @Override  
	 public IRuleConfigParser createRuleParser() {  
	        return new JsonRuleConfigParser();  
	 }  
	  
     @Override  
	 public ISystemConfigParser createSysParser() {  
	        return new JsonSystemConfigParser();  
	 }  
}
public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory{  
     @Override  
	 public IRuleConfigParser createRuleParser() {  
	        return new XmlRuleConfigParser();  
	 }  
  
     @Override  
	 public ISystemConfigParser createSysParser() {  
	        return new XmlSystemConfigParser();  
	 }  
}

//省略YamlConfigParserFactory和PropertiesConfigParserFactory代码

工厂模式的使用场景

工厂模式的功能是将对象的创建过程封装起来,对象的创建和对象的使用相分离。

第一种情况:类似规则配置解析的例子,代码中存在 if-else 分支判断,动态地根据不同的类型创建不同的对象。针对这种情况,我们就考虑使用工厂模式,将这一大坨 if-else创建对象的代码抽离出来,放到工厂类中。

还有一种情况,尽管我们不需要根据不同的类型创建不同的对象,但是,单个对象本身的创建过程比较复杂,比如前面提到的要组合其他类对象,做各种初始化操作。在这种情况下,我们也可以考虑使用工厂模式,将对象的创建过程封装到工厂类中。

是否需要使用工厂模式的思维标准:

  • 封装变化:创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明。
  • 代码复用:创建代码抽离到独立的工厂类之后可以复用。
  • 隔离复杂性:封装复杂的创建逻辑,调用者无需了解如何创建对象。
  • 控制复杂度:将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁。
0

评论区