如果这篇博客帮助到你,可以请我喝一杯咖啡~
CC BY 4.0 (除特别声明或转载文章外)
工厂模式
工厂模式一般分为:简单工厂、工厂方法和抽象工厂
简单工厂(Simple Factory)
首先,我们来看,什么是简单工厂模式。我们通过一个例子来解释一下。
在下面这段代码中,我们根据配置文件的后缀(json、xml、yaml、properties),选择不同的解析器(JsonRuleConfigParser、XmlRuleConfigParser……),将存储在文件中的配置解析成内存对象 RuleConfig
RuleConfigSource = class("RuleConfigSource")
function RuleConfigSource:load(ruleConfigFilePath)
local parser = nil
-- 这里的funcForJudge是伪代码 指的是一些复杂的if条件判断
-- 这里的doSthToInit也是伪代码,指的是new出来后还会对对象做一些复杂的处理
if funcForJudgeJSON(ruleConfigFilePath) then
parser = JsonRuleConfigParser.new()
doSthToInitJson(parser)
elseif funcForJudgeXML(ruleConfigFilePath) then
parser = XmlRuleConfigParser.new()
doSthToInitXml(parser)
elseif funcForJudgeYAML(ruleConfigFilePath) then
parser = YamlRuleConfigParser.new()
doSthToInitYaml(parser)
elseif funcForJudgePROP(ruleConfigFilePath) then
parser = PropertiesRuleConfigParser.new()
doSthToInitProp(parser)
else
error("Rule config file format is not supported: " + ruleConfigFilePath))
end
-- 从ruleConfigFilePath文件中读取配置文本到configText中
local configText = ReadContentFrom(ruleConfigFilePath)
-- 将configText传入解析器中
local ruleConfig = parser.parse(configText)
return ruleConfig
end
为了让代码逻辑更加清晰,可读性更好,我们要善于将功能独立的代码块封装成函数。按照这个设计思路,我们可以将代码中涉及 parser 创建的部分逻辑剥离出来,抽象成 createParser() 函数。重构之后的代码如下所示:
function RuleConfigSource:load(ruleConfigFilePath)
local parser = self:createParser(ruleConfigFilePath)
if not parser then
error("Rule config file format is not supported: " + ruleConfigFilePath))
end
-- 从ruleConfigFilePath文件中读取配置文本到configText中
local configText = ReadContentFrom(ruleConfigFilePath)
-- 将configText传入解析器中
local ruleConfig = parser.parse(configText)
return ruleConfig
end
function RuleConfigSource:createParser(path)
local parser = nil
if funcForJudgeJSON(path) then
parser = JsonRuleConfigParser.new()
doSthToInitJson(parser)
elseif funcForJudgeXML(path) then
parser = XmlRuleConfigParser.new()
doSthToInitXml(parser)
elseif funcForJudgeYAML(path) then
parser = YamlRuleConfigParser.new()
doSthToInitYaml(parser)
elseif funcForJudgePROP(path) then
parser = PropertiesRuleConfigParser.new()
doSthToInitProp(parser)
end
return parser
end
为了让类的职责更加单一、代码更加清晰,我们还可以进一步将 createParser() 函数剥离到一个独立的类中,让这个类只负责对象的创建。而这个类就是我们现在要讲的简单工厂模式类。具体的代码如下所示:
function RuleConfigSource:load(ruleConfigFilePath)
local parser = RuleConfigParserFactory.createParser(ruleConfigFilePath)
if not parser then
error("Rule config file format is not supported: " + ruleConfigFilePath))
end
-- 从ruleConfigFilePath文件中读取配置文本到configText中
local configText = ReadContentFrom(ruleConfigFilePath)
-- 将configText传入解析器中
local ruleConfig = parser.parse(configText)
return ruleConfig
end
RuleConfigParserFactory = class("RuleConfigParserFactory")
function RuleConfigParserFactory:createParser(path)
local parser = nil
if funcForJudgeJSON(path) then
parser = JsonRuleConfigParser.new()
doSthToInitJson(parser)
elseif funcForJudgeXML(path) then
parser = XmlRuleConfigParser.new()
doSthToInitXml(parser)
elseif funcForJudgeYAML(path) then
parser = YamlRuleConfigParser.new()
doSthToInitYaml(parser)
elseif funcForJudgePROP(path) then
parser = PropertiesRuleConfigParser.new()
doSthToInitProp(parser)
end
return parser
end
在上面的代码实现中,我们每次调用 RuleConfigParserFactory 的 createParser() 的时候,都要创建一个新的 parser。实际上,如果 parser 可以复用,为了节省内存和对象创建的时间,我们可以将 parser 事先创建好缓存起来。当调用 createParser() 函数的时候,我们从缓存中取出 parser 对象直接使用。
这有点类似单例模式和简单工厂模式的结合,具体的代码实现如下所示。在接下来的讲解中,我们把上一种实现方法叫作简单工厂模式的第一种实现方法,把下面这种实现方法叫作简单工厂模式的第二种实现方法。
RuleConfigParserFactory = class("RuleConfigParserFactory")
function RuleConfigParserFactory:ctor()
self.parserList = {
["json"] = JsonRuleConfigParser.new(),
["xml"] = XmlRuleConfigParser.new(),
["yaml"] = YamlRuleConfigParser.new(),
["properties"] = PropertiesRuleConfigParser.new(),
}
end
function RuleConfigParserFactory:createParser(path)
if path then
local parser = nil
if funcForJudgeJSON(path) then
parser = self.parserList["json"]
doSthToInitJson(parser)
elseif funcForJudgeXML(path) then
parser = self.parserList["xml"]
doSthToInitXml(parser)
elseif funcForJudgeYAML(path) then
parser = self.parserList["yaml"]
doSthToInitYaml(parser)
elseif funcForJudgePROP(path) then
parser = self.parserList["properties"]
doSthToInitProp(parser)
end
return parser
end
end
对于上面两种简单工厂模式的实现方法,如果我们要添加新的 parser
或者修改初始化处理,那势必要改动到 RuleConfigParserFactory
的代码,这个里面除了有我当前自己想改的 parser
的初始化代码,还有其他的,那这是不是违反开闭原则呢?实际上,如果不是需要频繁地添加新的 parser
,只是偶尔修改一下 RuleConfigParserFactory
代码,稍微不符合开闭原则,也是完全可以接受的。
总结一下,尽管简单工厂模式的代码实现中,有多处 if
分支判断逻辑,违背开闭原则,但权衡扩展性和可读性,这样的代码实现在大多数情况下(比如,不需要频繁地添加 parser
,也没有太多的 parser
)是没有问题的。
工厂方法(Factory Method)
如果在上面的例子中 funcForJudge
和 doSthToInit
很多且很复杂,那么将这些处理全部放到一个类里面去处理,就会显得很复杂,且新增一个 parser
时,就会很麻烦。此时我们可以通过多态的思路将上面的 if
分支进行重构:
我们将上面 if
分支里面的代码块抽离出来,独立为一个工厂类,里面是对象创建以及所有的创建处理,然后再实现一个这些工厂类的工厂,用于管理这些工厂。
BaseFactory = class("BaseFactory")
function BaseFactory:createParser()
-- 将创建parser的处理,即doSthToInit放到createParser中
end
JsonRuleConfigParserFactory = class("JsonRuleConfigParserFactory", BaseFactory)
function JsonRuleConfigParserFactory:createParser()
local parser = self.parser or JsonRuleConfigParser.new()
doSthToInitJson(parser)
return parser
end
XmlRuleConfigParserFactory = class("XmlRuleConfigParserFactory", BaseFactory)
function XmlRuleConfigParserFactory:createParser()
local parser = self.parser or XmlRuleConfigParser.new()
doSthToInitXml(parser)
return parser
end
YamlRuleConfigParserFactory = class("YamlRuleConfigParserFactory", BaseFactory)
function YamlRuleConfigParserFactory:createParser()
local parser = self.parser or YamlRuleConfigParser.new()
doSthToInitYaml(parser)
return parser
end
PropertiesRuleConfigParserFactory = class("PropertiesRuleConfigParserFactory", BaseFactory)
function PropertiesRuleConfigParserFactory:createParser()
local parser = self.parser or PropertiesRuleConfigParser.new()
doSthToInitProp(parser)
return parser
end
RuleConfigParserFactoryMap = class("RuleConfigParserFactoryMap") -- 工厂的工厂
function RuleConfigParserFactoryMap:ctor()
self.cachedFactories = {
["json"] = JsonRuleConfigParserFactory.new(),
["xml"] = XmlRuleConfigParserFactory.new(),
["yaml"] = YamlRuleConfigParserFactory.new(),
["properties"] = PropertiesRuleConfigParserFactory.new(),
}
end
function RuleConfigParserFactoryMap:getParserFactory(path)
if path then
local parserFactory = nil
if funcForJudgeJSON(path) then
parserFactory = self.cachedFactories["json"]
elseif funcForJudgeXML(path) then
parserFactory = self.cachedFactories["xml"]
elseif funcForJudgeYAML(path) then
parserFactory = self.cachedFactories["yaml"]
elseif funcForJudgePROP(path) then
parserFactory = self.cachedFactories["properties"]
end
return parserFactory
end
end
function RuleConfigSource:load(ruleConfigFilePath)
local parserFactory = RuleConfigParserFactoryMap:getParserFactory(ruleConfigFilePath)
if not parserFactory then
error("Rule config file format is not supported: " + ruleConfigFilePath))
end
local parser = parserFactory:createParser()
-- 从ruleConfigFilePath文件中读取配置文本到configText中
local configText = ReadContentFrom(ruleConfigFilePath)
-- 将configText传入解析器中
local ruleConfig = parser.parse(configText)
return ruleConfig
end
当我们需要添加新的规则配置解析器的时候,我们只需要创建新的 parser
类和 parser factory
类,并且在 RuleConfigParserFactoryMap
类中,将新的 parser factory
对象添加到 cachedFactories
中即可。代码的改动非常少,基本上符合开闭原则。如果我们要修改某个 parser
的初始化处理,也只需要找到其自己对应的 Factory
即可
那什么时候该用工厂方法模式,而非简单工厂模式呢?
我们前面提到,之所以将某个代码块剥离出来,独立为函数或者类,原因是这个代码块的逻辑过于复杂,剥离之后能让代码更加清晰,更加可读、可维护。但是,如果代码块本身并不复杂,就几行代码而已,我们完全没必要将它拆分成单独的函数或者类。
当对象的创建逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作的时候,我们推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂。而使用简单工厂模式,将所有的创建逻辑都放到一个工厂类中,会导致这个工厂类变得很复杂。
除此之外,在某些场景下,如果对象不可复用,那工厂类每次都要返回不同的对象。如果我们使用简单工厂模式来实现,就只能选择第一种包含 if 分支逻辑的实现方式。如果我们还想避免烦人的 if-else 分支逻辑,这个时候,我们就推荐使用工厂方法模式。
抽象工厂(Abstract Factory)
在简单工厂和工厂方法中,类只有一种分类方式。比如,在规则配置解析那个例子中,解析器类只会根据配置文件格式(Json、Xml、Yaml……)来分类。但是,如果类有两种分类方式,比如,我们既可以按照配置文件格式来分类,也可以按照解析的对象(Rule 规则配置还是 System 系统配置)来分类,如果还是继续用工厂方法来实现的话,我们要针对每个 parser 都编写一个工厂类,也就是要编写 8 个工厂类。如果我们未来还需要增加针对业务配置的解析器(比如 IBizConfigParser),那就要再对应地增加 4 个工厂类。而我们知道,过多的类也会让系统难维护。这个问题该怎么解决呢?
抽象工厂就是针对这种非常特殊的场景而诞生的。我们可以让一个工厂负责创建多个不同类型的对象(IRuleConfigParser、ISystemConfigParser
等),而不是只创建一种 parser
对象。这样就可以有效地减少工厂类的个数:
BaseFactory = class("BaseFactory")
function BaseFactory:createRuleParser()
-- 将创建parser的处理,即doSthToInit放到createParser中
end
function BaseFactory:createSystemParser()
-- 将创建parser的处理,即doSthToInit放到createParser中
end
JsonConfigParserFactory = class("JsonConfigParserFactory", BaseFactory)
function JsonConfigParserFactory:createRuleParser()
local parser = self.parser or JsonRuleConfigParser.new()
doSthToInitRuleJson(parser)
return parser
end
function JsonConfigParserFactory:createSystemParser()
local parser = self.parser or JsonSystemConfigParser.new()
doSthToInitSystemJson(parser)
return parser
end
总结
工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类
抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类
- 简单工厂(Simple Factory 当每个对象的创建逻辑都比较简单的时候,将多个对象的创建逻辑放到一个工厂类中。
- 工厂方法(Factory Method) 当每个对象的创建逻辑都比较复杂的时候,为了避免设计一个过于庞大的简单工厂类时,将创建逻辑拆分得更细,每个对象的创建逻辑独立到各自的工厂类中。并设计一个工厂的工厂来作为对外的创建接口。
- 抽象工厂(Abstract Factory)- 不常用 让一个工厂负责创建多个不同类型的对象
应用
我们项目里面的场景对象类就使用了抽象工厂的方式:
可以看到我们外部通过使用 SceneFactory
这个单例的接口去获取不同的工厂,然后再由工厂生成自己的对象。对于 MonsterFactory
,它能生产至少5种不同的对象。
当我们要新增一个场景类对象时,我们只需要为其新增一个工厂,并将该工厂添加到 SceneFactory
中即可,创建该场景类的所有操作都在其自己的工厂中进行,符合开闭原则。