工厂模式(Factory Pattern)是设计模式中非常基础且常用的一种模式,主要目的是通过封装对象的创建过程,从而实现代码的解耦和灵活性的提升。
工厂模式的核心思想
工厂模式又细分为三种类型,简单工厂、工厂方法和抽象工厂,其中工厂方法和抽象工厂被收录在23种设计模式中,而简单工厂被看做是工厂方法的一种特例。其实这三种工厂模式的原理和实现都非常简单,重点在于搞清应用场景,什么时候可以考虑使用工厂模式,又该用这三种类型的哪一种呢?
举个例子:
应用需要根据传入的路径去解析文件并显示,但文件可能有json、xml或yaml等格式,需要根据后缀名来创建解析实现类。
需求很简单,可以直接翻译成代码:
fun loadConfig(path: String): Config? {
var parser : IRuleConfigParser? = null
if (path.endsWith("json", true)) {
parser = JsonRuleParser()
} else if (path.endsWith("xml", true)) {
parser = XmlRuleParser()
} else if (path.endsWith("yaml", true)) {
parser = YamlRuleParser()
}
val config = parser?.parser(path)
return config
}
但“根据文件后缀名创建解析器”的逻辑是相对独立的,可以将其抽成一个方法来复用,但其实还可以将这部分逻辑抽成一个类,让这个类来负责对象的创建,从而更符合单一职责原则,让代码更清晰,而抽出的类就是简单工厂模式了。
又称静态工厂方法模式(Static Factory Method Pattern),简单工厂模式不是一种设计模式,而更像是一种编程习惯。抽出的工厂类根据传入的参数,决定创建出哪一种类的实例。
下面用简单工厂封装一下对象创建的代码:
//简单工厂
class SimpleFactory {
companion object {
@JvmStatic
fun create(path: String): IRuleConfigParser? {
return if (path.endsWith("json", true)) {
JsonRuleParser()
} else if (path.endsWith("xml", true)) {
XmlRuleParser()
} else if (path.endsWith("yaml", true)) {
YamlRuleParser()
} else null
}
}
}
调用处:
fun loadConfig(path: String): Config? {
val parser = SimpleFactory.create(path)
val config = parser?.parser(path)
return config
}
可以看到调用处代码非常简洁,创建代码都被封装到工厂类中了,开发中只要维护工厂代码即可。其实该例子创建的对象是可以复用的,那么还可以在工厂类中通过 map 缓存创建的对象,不必每次都创建新对象。同时 map 还可以取代 if-else 形式的代码:
//带缓存的简单工厂
class SimpleFactory {
companion object {
@JvmStatic
private val cacheMap: HashMap<String, IRuleConfigParser> = hashMapOf(
"json" to JsonRuleParser(),
"xml" to XmlRuleParser(),
"yaml" to YamlRuleParser(),
)
@JvmStatic
fun create(path: String): IRuleConfigParser? {
val extension = getFileExtension(path)
return cacheMap[extension]
}
@JvmStatic
fun getFileExtension(filePath: String): String {
//...解析文件名获取扩展名,比如rule.json,返回json
return filePath.substringAfterLast('.', "")
}
}
}
简单工厂模式的问题是,如果需要扩展新的产品类,就需要修改工厂类的代码,违反了开闭原则,所以只适合改动不频繁的简单场景(设计原则没必要严格遵守,根据场景灵活使用才是目的)
这里的解析器创建只是简单的 new 了一个对象,如果解析器的创建流程变得复杂,那 create() 方法中的代码也会随之变得冗长,这种情况下可以把解析器复杂的创建逻辑进一步封装到各个工厂类中,这就成了工厂方法模式。
工厂方法模式定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法让类的实例化延迟到子类中进行。这种方式的好处是,新增产品类时,只需要增加一个具体的产品类和对应的具体工厂类,而不需要修改原有的工厂接口和工厂类。
下面使用工厂方法封装解析器创建流程:
//封装每个解析器的创建过程
interface IRuleConfigParserFactory {
fun create(): IRuleConfigParser
}
//Json解析器创建
class JsonParserFactory : IRuleConfigParserFactory {
override fun create(): IRuleConfigParser {
val parser = JsonRuleParser()
//parser.xxx = xxx
//...
return parser
}
}
//Xml解析器创建
class XmlParserFactory: IRuleConfigParserFactory {
override fun create(): IRuleConfigParser = XmlRuleParser()
}
//Yaml解析器创建
class YamlParserFactory: IRuleConfigParserFactory {
override fun create(): IRuleConfigParser = YamlRuleParser()
}
//这里其实还是简单工厂
class MethodFactory {
companion object {
@JvmStatic
fun create(path: String): IRuleConfigParser? {
var parserFactory: IRuleConfigParserFactory? = null
if (path.endsWith("json", true)) {
parserFactory = JsonParserFactory()
} else if (path.endsWith("xml", true)) {
parserFactory = XmlParserFactory()
} else if (path.endsWith("yaml", true)) {
parserFactory = YamlParserFactory()
}
return parserFactory?.create()
}
}
}
外部调用:
fun loadConfig(path: String): Config? {
val parser = MethodFactory.create(path)
val config = parser?.parser(path)
return config
}
可以看到复杂的解析器创建逻辑被隔离到具体的工厂创建类中了,又用简单工厂简化了调用代码。可以发现,简单工厂侧重对分支代码的封装,而工厂方法更侧重隔离对象创建的复杂代码,两者还可以组合使用。
工厂方法缺点是,如果工厂不只是生产一种类型的对象,或者说创建的对象会根据多个维度分类,那工厂方法的实现类将会非常多,从而难以维护。如上面的例子,解析器除了根据文件格式分类,还增加了文件大小的维度,这时候需要实现的类就有 3 x 2 = 6个:
SmallJsonParserFactory
SmallXmlParserFactory
SmallYamlParserFactory
BigJsonParserFactory
BigXmlParserFactory
BigYamParserFactory
可见创建对象维度的增加会导致工厂类爆增,针对上面场景,就可以使用抽象工厂,让一个工厂可以生产不同类型的对象。
抽象工厂模式使用抽象的接口来创建一系列相关或相互依赖的对象,而无需指定它们具体的类。这种方式主要用于创建一组相互依赖或关联的对象,而不需要指定它们具体的类。
以上面新增文件大小维度举例,抽象出代码如下:
//抽象工厂
interface IConfigParserFactory {
fun createJsonFactory(): IRuleConfigParser
fun createXmlFactory(): IRuleConfigParser
fun createYamlFactory(): IRuleConfigParser
}
//大文件解析器
class ConfigBigParser: IConfigParserFactory {
override fun createJsonFactory(): IRuleConfigParser {
//再这里创建不同维度的产品类
return JsonRuleParser()
}
override fun createXmlFactory(): IRuleConfigParser {
return XmlRuleParser()
}
override fun createYamlFactory(): IRuleConfigParser {
return YamlRuleParser()
}
}
//小文件解析器
class ConfigSmallParser: IConfigParserFactory {
override fun createJsonFactory(): IRuleConfigParser {
//..省略
return JsonRuleParser()
}
override fun createXmlFactory(): IRuleConfigParser {
//..省略
return XmlRuleParser()
}
override fun createYamlFactory(): IRuleConfigParser {
//..省略
return YamlRuleParser()
}
}
调用处代码:
fun loadConfig3(path: String): Config? {
//可根据具体业务,通过抽象工厂创建不同的产品类,无须关心实现类
val parser1 = ConfigBigParser()
val parser2 = ConfigSmallParser()
parser1.createJsonFactory()
parser1.createXmlFactory()
parser1.createYamlFactory()
parser2.createJsonFactory()
parser2.createXmlFactory()
parser2.createYamlFactory()
}
抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。
工厂模式是将对象创建的过程封装,创建对象的复杂代码被隔离到每个工厂内部消化,从而实现对象的创建与使用分离。不同工厂模式适用于不同的场景,选择哪种模式取决于具体的需求和场景。