- 本文总结自《设计模式之美》——王争。大家有条件可以去极客时间或者其他渠道找来阅读和学习,同时推荐《数据结构与算法之美》
- 设计模式经典书籍,《设计模式》—— GoF
工厂模式细分可以分为:简单工厂、工厂方法、抽象工厂三种。也可以分为:工厂方法、抽象工厂两种。前一种分法更为常见,所以按照前一种细分来梳理。其中前两种(简单工厂和工厂方法)比较常见常用,后一种并不常用。设计模式更像是一个模板化的公式,所以更重要的应该是了解到什么情况下需要使用工厂模式以及其优劣势。
其中很重要的一点就是要弄明白:使用工厂模式,相比于new一个对象有什么优势?
1. 简单工厂(simple factory)
首先看下面这段代码:
public class RuleConfigSource {
public RuleConfig load(String ruleConfigFilePath) {
String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
// 重点关注 begin
IRuleConfigParser parser = null;
if ("json".equalsIgnoreCase(ruleConfigFileExtension)) {
parser = new JsonRuleConfigParser();
} else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)) {
parser = new XmlRuleConfigParser();
} else {
throw new InvalidRuleConfigException(
"Rule config file format is not supported: " + ruleConfigFilePath);
}
// 重点关注 end
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);
// 代码抽离出去 begin
IRuleConfigParser parser = createParser(ruleConfigFileExtension);
if (parser == null){
throw new InvalidRuleConfigException("Rule config not supported: " + ruleConfigFileExtension);
}
// 代码抽离出去 end
String configText = "";
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(String filePath) {
//...解析文件名获取扩展名,比如rule.json,返回json
return "json";
}
// 根据文件扩展名创建对应的解析器
private IRuleConfigParser createParser(String ruleConfigFileExtension){
if ("json".equalsIgnoreCase(ruleConfigFileExtension)){
return new JsonRuleConfigParser();
} else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)){
return new XmlRuleConfigParser();
} else {
return null;
}
}
}
为了让类的职责更加单一(单一职责原则),上述代码中的 createParser
方法可以抽离到一个单独的类中,当然这个类的单一职责就是负责创建一个合适的Parser。这也就是所谓的简单工厂模式。如下所示:
public class RuleConfigSource {
public RuleConfig load(String ruleConfigFilePath) {
String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
// 代码抽离称为一个类 begin
IRuleConfigParser parser = RuleParserFactory.createParser(ruleConfigFileExtension); // 使用静态方法的方式调用
if (parser == null){
throw new InvalidRuleConfigException("Rule config not supported: " + ruleConfigFileExtension);
}
// 代码抽离称为一个类 end
String configText = "";
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(String filePath) {
//...解析文件名获取扩展名,比如rule.json,返回json
return "json";
}
}
// ----------------- 下面是 简单工厂类 --------------------
public class RuleParserFactory{
public static IRuleConfigParser createParser(String ruleConfigFileExtension){
if ("json".equalsIgnoreCase(ruleConfigFileExtension)){
return new JsonRuleConfigParser();
} else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)){
return new XmlRuleConfigParser();
} else {
return null;
}
}
}
大部分的工厂类都是以Factory(中文译为“工厂”)结尾的,但是也不是必须的,例如Java当中的 DataFormat
、Calender
。此外,通过工厂生产对象的方法一般都是用 create 开始的,但是也不是死规定,例如也可以命名为 getInstance()
、createInstance()
、newInstance()
等,或者Java中包装类型的方法命名为 valueOf()
。
上述的工厂方法,每次使用 create 方法生成对象的时候,都会返回一个新的对象。为了节省内存和时间,可以将创建的对象缓存起来进行复用,当需要创建的时候,就可以直接返回缓存起来的对象。有点类似单例模式和简单工厂模式的结合。代码如下:
public class RuleConfigSource {
public RuleConfig load(String ruleConfigFilePath) {
String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
IRuleConfigParser parser = RuleParserFactory.createParser(ruleConfigFileExtension); // 使用静态方法的方式调用
if (parser == null){
throw new InvalidRuleConfigException("Rule config not supported: " + ruleConfigFileExtension);
}
String configText = "";
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(String filePath) {
//...解析文件名获取扩展名,比如rule.json,返回json
return "json";
}
}
// ----------------- 下面是 简单工厂类 --------------------
public class RuleParserFactory{
public static final Map<String, IRuleConfigParser> cachedParsers = new HashMap<>();
// 静态代码块。类加载的时候执行,并且只执行一次。如果有多个静态代码块,按照顺序执行。用来初始化
static {
cachedParsers.put("json", new JsonRuleConfigParser());
cachedParsers.put("xml", new XmlRuleConfigParser());
}
public static IRuleConfigParser createParser(String ruleConfigFileExtension){
if (ruleConfigFileExtension == null || ruleConfigFileExtension.isEmpty()){
return null;
}
return cachedParsers.get(ruleConfigFileExtension.toLowerCase());
}
}
这就是简单工厂模式。总结来说就是,将创建对象的逻辑单独拿出来抽象为一个类,该类称为工厂类。可以通过缓存的方式避免每次都需要创建一个新的对象。
下面思考一下,如果我们要增加工厂创建对象的种类(例如除了json和xml,还要增加md类型)。我们需要改动的位置有哪些?需要改动Factory工厂类当中的代码,具体来说需要改动生成对象方法中的代码(当然还可能有缓存需要改)。当没有使用Map进行缓存的时候,需要进行多个 if 来判断需要生成对象的类型是什么,这时候可以使用多态(看后文)等各种方式来避免多个if分支判断逻辑,但是这样会增加类的数量,影响整体代码的可读性,所以需要权衡。
2. 工厂方法
如果想要去掉 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();
}
}
如何使用上述重构之后的工厂方法类呢?需要在load函数中创建出对应的工厂,进而使用相应的create方法来得到对象。如下所示:
public class RuleConfigSource {
public RuleConfig load(String ruleConfigFilePath) {
String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
// 重点关注 begin
IRuleConfigParserFactory factory = null;
if ("json".equalsIgnoreCase(ruleConfigFileExtension)){
factory = new JsonRuleConfigParserFactory();
} else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)){
factory = new XmlRuleConfigParserFactory();
} else {
throw new InvalidRuleConfigException("Rule config not supported: " + ruleConfigFileExtension);
}
IRuleConfigParser parser = factory.createParser();
// 重点关注 end
String configText = "";
//从ruleConfigFilePath文件中读取配置文本到configText中
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(String filePath) {
//...解析文件名获取扩展名,比如rule.json,返回json
return "json";
}
}
可以看到我们的if分支从之前的判断对象类型,变成了现在的判断工厂类型,可能看上去并没有变简单,反而是增加了一堆的类变复杂了。此时,我们如果要增加一个类型的话,需要修改的地方有:1. 增加一个实现IRuleConfigParserFactory的类;2. RuleConfigSource的load函数中对新的工厂类型做判断。
这时,我们可以为工厂类创建一个工厂,也就是工厂的工厂。看代码就一目了然了:
// 自上而下梳理一下需要的代码
// Part1 : 需要有每个解析器(Parser)的具体类,需要实现具有parser函数的接口。这里略过
// ......
// Part2 : 为上述每个类型创建一个实现了公共接口的具体工厂类
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();
}
}
// Part3 : 给上述每个具体工厂,创建一个工厂,即 “工厂的工厂”
public class RuleConfigParserFactoryMap{
private static final Map<String, IRuleConfigParserFactory> cachedFactories = new HashMap<>();
static {
cachedParsers.put("json", new JsonRuleConfigParser());
cachedParsers.put("xml", new XmlRuleConfigParser());
}
// 返回一个合适的工厂
public static IRuleConfigParserFactory getParserFactory(String extensionType){
if(extensionType == null || extensionType.isEmpty()){
return null;
}
return cachedFactories.get(extensionType.toLowerCase());
}
}
// Part4 : 使用上述类实现具体的load函数
public class RuleConfigSource {
public RuleConfig load(String ruleConfigFilePath) {
String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
// 主要改动部分 begin
IRuleConfigParserFactory factory = RuleConfigParserFactoryMap.getParserFactory(ruleConfigFileExtension);
if (factory == null) {
throw new InvalidRuleConfigException("Rule config not supported: " + ruleConfigFileExtension);
}
IRuleConfigParser parser = factory.createParser();
// 主要改动部分 end
String configText = "";
//从ruleConfigFilePath文件中读取配置文本到configText中
RuleConfig ruleConfig = parser.parse(configText);
return ruleConfig;
}
private String getFileExtension(String filePath) {
//...解析文件名获取扩展名,比如rule.json,返回json
return "json";
}
}
这时候,如果我们需要增加一个新的配置类(parser),需要进行的改动有:
- Part1:增加一个实现了parser函数接口的具体Parser类(这一步不管什么肯定要有)
- Part2:增加一个实现了IRuleConfigParserFactory的工厂类(用于创建上述Parser类)
- Part3:static当中put进一个新的工厂类实例
经过上述改动,就可以实现新增一个Parser的功能。可以看到对原来代码的修改只有Part3,其他部分都是新增类,并没有对代码进行修改,还是比较有扩展型的(符合开闭原则:对扩展开发,对修改关闭)。
3. 简单工厂 vs 工厂方法
这么多的类和方法可能已经眼花缭乱了。下面进行一些总结。
首先总结一下简单工厂的两种实现方式,主要有以下两种:
- 创建一个工厂类,内部维护一个静态方法 createInstance() ,根据传入的参数 new 一个对象返回即可
- 在1的基础上,增加一个Map缓存,避免每次都new一个新的对象。
其实上述两种方式本质并无区别,也就是将创建对象的逻辑从原本的方法中抽离到一个单独的工厂类当中,后续只需要修改工厂类中的生成对象的方法即可,是否增加缓存只是修改工厂类代码量多少以及复用的区别。
然后总结一下工厂方法的实现步骤 :
- 针对每个对象都创建一个实现了公共接口的工厂类(每种对象对应一个工厂类)
- 针对上述所有工厂创建一个统一的工厂类(工厂的工厂,内部可以使用Map进行复用和维护)
- 使用的时候,先获得一个合适的工厂类,然后通过多态的方式创建对象。
为了更好的理解工厂方法,简单的画个图(并不是严格的UML图):
下面一个问题是,我们应该在何时使用简单工厂,何时使用工厂方法呢?
如果代码创建的逻辑十分简单,是没有必要单独使用一个工厂来创建该对象的。只有当对象的创建逻辑比较负责,不是简单的new一下就可以的时候,例如需要组合各种其他对象,做各种初始化的时候,就可以考虑使用工厂设计模式了。考虑以下两种情况:
- 简单工厂是将所有的对象的创建逻辑放到一个类当中,工厂方法是将每一个对象的创建逻辑放到单独的类当中。所以,如果我们有很多的对象,而且每个逻辑都不简单,则考虑使用工厂方法,否则的话可以考虑用简单工厂。
- 如果对象不可以复用,则使用简单工厂的时候就肯定需要多个判断分支来实现。这时候可以考虑用工厂方法避免多个判断分支
4. 抽象工厂
抽象工厂并不常见,所以作为一个了解即可。直接先贴一下改动上述代码之后的结果:
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();
}
}
工厂方法最终的目的是创建出一个符合要求的对象。但是我们有时候针对对象(或者说类)有很多种分类方式,有点类似于Mac系统中的交叉索引
根据上述的图,不使用抽象工厂,需要创建的工厂个数为4个(文件类型Json、Xml两种,解析对象配置类型Rule、Systeml两种)。但是使用了抽象工厂,就可以将工厂个数缩减为2个。了解一下就好。
5. 总结
工厂模式有三种:简单工厂(静态工厂)、工厂方法、抽象工厂。重点掌握前两种,第三种作为了解即可。
当创建一个对象的过程比较复杂,而且可能有多种相似的对象(例如通过Json或者xml格式解析文件),就可以考虑使用工厂设计模式了。具体来说,当创建逻辑是以下情况的时候,可以考虑使用:
- 类似本文的的规则解析配置这种,代码中有比较多的 if-else 分支来判断是哪种类型,这时候就可以将这段判断逻辑抽离出来
- 如果我们不需要根据不同的类型创建不同的对象,但是创建一个对象比较复杂,例如包含比较多的初始化,也可以考虑抽离出来。
至于简单工厂和工厂方法的选用,第三节已经阐述过了。
最后,宏观总结一下工厂设计模式的作用:
- 封装变化:创建对象的逻辑可能发生变化,封装成单独的类的时候,创建的逻辑对调用者是透明的
- 代码复用:创建代码抽离到工厂类之后可以复用,
- 隔离复杂性:封装复杂的创建逻辑,调用者无需了解具体实现
- 控制复杂度:让类和函数的职责更加单一,易于维护,代码简洁
最后再提一句。本文只是 《设计模式之美》系列的一个笔记。强烈推荐大家有条件去阅读原文~