工厂模式简单来说就是用来创建对象。 工厂模式分为三种更加细分的类型:简单工厂、工厂方法和抽象工厂,一般认为简单工厂是工厂方法的特例,我会通过这篇文章对简单工厂和工厂方法进行讲述。
本文UML类图链接为:https://www.processon.com/view/link/6080def6079129456d4beecf
本文代码链接为:https://github.com/shidawuhen/asap/blob/master/controller/design/7factory.go
1.定义
1.1 工厂方法
工厂方法:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
工厂方法UML类图:
简单工厂UML类图:
1.2工厂方法分析
通过定义和类图,我们可以做出以下分析:
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
- 标准的工厂方法需要有一个工厂类接口,就是UML中的Creator。这个接口的作用是为了实现里氏替换原则:子类对象能够替换程序中父类对象出现的任何地方。这个接口的存在能够保证代码的优雅。
- UML中还有一个Product接口,这个接口的存在有三重含义
- 表明工厂方法创建的工厂,其功能应该是类似的,可以抽象出一个父类
- 符合里氏替换原则,使代码优雅
- 符合依赖倒转原则:高层模块不要依赖低层模块。高层模块和低层模块应该通过抽象来互相依赖。而Creator和Product正是依赖关系
- 简单工厂和工厂方法相比,缺一个Creator接口,只有一个真实的工厂类。这导致两者在实现和场景上都有区别。
- 实现上:简单工厂直接返回ConcreteProduct;工厂方法先返回指定工厂,然后通过指定工厂创建ConcreteProduct
- 场景上:使用简单工厂还是工厂方法标准只有一条,创建Product是否复杂,复杂就用工厂方法,如果只需new,建议使用简单工厂
对于设计原则,大家可以看我的这篇文章:Go设计模式(3)-设计原则
2.使用场景
工厂模式一般用于对于不同的场景,需要创建不同的对象,但是这些对象实现的功能是很相似的,可以抽象出一个父类,对于这种情形就可以使用工厂模式。
在实际中,很多框架都支持多种配置文件,项目启动时解析配置文件,将文件内容写入到内存中。配置文件格式很多,有xml、json、yaml等,这个时候就需要根据后缀来解析文件,使用工厂模式就很合理。
3.代码实现
假设你设计的框架支持xml、yaml格式,对于解析部分的代码我们分别使用简单工厂和工厂方法来进行实现。
package main
import "fmt"
/**
* @Description: 解析接口,有解析函数用来解析文件
*/
type ConfigParse interface {
Parse(path string) string
}
/**
* @Description: Json解析类,实现解析接口
*/
type JsonConfigParse struct {
}
/**
* @Description: 用于解析Json文件
* @receiver json
* @param path
* @return string
*/
func (json *JsonConfigParse) Parse(path string) string {
return "解析json配置文件,路径为:" + path
}
/**
* @Description: Yaml解析类,实现解析接口
*/
type YamlConfigParse struct {
}
/**
* @Description: 用于解析Yaml文件
* @receiver yaml
* @param path
* @return string
*/
func (yaml *YamlConfigParse) Parse(path string) string {
return "解析yaml配置文件,路径为:" + path
}
/**
* @Description: 简单工厂
*/
type SimpleParseFactory struct {
}
func (simple *SimpleParseFactory) create(ext string) ConfigParse {
switch ext {
case "json":
return &JsonConfigParse{}
case "yaml":
return &YamlConfigParse{}
}
return nil
}
/**
* @Description: 工厂方法
*/
type NormalParseFactory interface {
createParse() ConfigParse
}
/**
* @Description: Json工厂方法
*/
type JsonNormalParseFactory struct {
}
/**
* @Description: 该方法用于创建Json解析器
* @receiver jsonFactory
* @param ext
* @return ConfigParse
*/
func (jsonFactory *JsonNormalParseFactory) createParse() ConfigParse {
//此处假装有各种组装
return &JsonConfigParse{}
}
/**
* @Description: Yaml解析工厂
*/
type YamlNormalParseFactory struct {
}
/**
* @Description: 该方法用于创建Yaml解析器
* @receiver yamlFactory
* @return ConfigParse
*/
func (yamlFactory *YamlNormalParseFactory) createParse() ConfigParse {
//此处假装有各种组装
return &YamlConfigParse{}
}
/**
* @Description: 根据后缀创建工厂
* @param ext
* @return NormalParseFactory
*/
func createFactory(ext string) NormalParseFactory {
switch ext {
case "json":
return &JsonNormalParseFactory{}
case "yaml":
return &YamlNormalParseFactory{}
}
return nil
}
func main() {
//简单工厂使用代码
fmt.Println("------------简单工厂")
simpleParseFactory := &SimpleParseFactory{}
parse := simpleParseFactory.create("json")
if parse != nil {
data := parse.Parse("conf/config.json")
fmt.Println(data)
}
//工厂使用代码
fmt.Println("------------工厂方法")
factory := createFactory("yaml")
parse = factory.createParse()
if parse != nil {
data := parse.Parse("conf/config.yaml")
fmt.Println(data)
}
}
返回为:
➜ myproject go run main.go
——————简单工厂
解析json配置文件,路径为:conf/config.json
——————工厂方法
解析yaml配置文件,路径为:conf/config.yaml
大家看这个代码,会感觉简单工厂和工厂模式似像非像。有这种感觉是对的,因为这两者真的是有部分相似、有部分不相似。
相似部分:SimpleParseFactory的create函数和createFactory函数,都有类型的判断,这部分两种方式都有,谁也没省掉。
不同部分:SimpleParseFactory的create直接创建ConfigParse,createFactory只创建出工厂,由调用方调用工厂的创建方法获得ConfigParse
简单工厂和工厂方法在开放-封闭原则上是一致的,按照我的这种写法,增加新的解析器,对于扩展是开放的,不会影响以前的代码,但是分别需要在SimpleParseFactory的create或createFactory增加后缀判断,所以一定程度上都违背了封闭原则。当然,如果想不违背封闭原则也可以,都改为从走配置的即可,这种方案能够提升两者的封闭性。
这个例子也很好的说明了使用两者的区别,创建简单就用简单工厂,创建复杂就用工厂方法,代价就是类的数量会增加。
总结
工厂方法是经常使用的设计模式,需要好好掌握。工厂方法是里氏替换原则、依赖倒转原则、开放-封闭原则的体现,在具体实现上,主要使用了接口与实现的语法。本文也介绍了简单工厂和工厂方法的相似点和区别点。
在扩展方面,除了可以增加通过读取配置的方案增加封闭性,还可以通过在工厂内部提前建好解析类,这样能够去掉解析类,还能实现单例的功能。工厂方法还有很多其它玩法,大家可以自行研究。
其实工厂方法真的提高了灵活性,假设你开发了一个框架,只能解析yaml,代码开源到github上,这时会有很多小伙伴来增强你的代码,如果解析方面你使用了工厂方法,那么其他人能够很方便的添加新的解析功能,同时对原有逻辑影响降到最低,这就是优雅。
最后
大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)
我的个人博客为:https://shidawuhen.github.io/