工厂模式简单来说就是用来创建对象。 工厂模式分为三种更加细分的类型:简单工厂、工厂方法和抽象工厂,一般认为简单工厂是工厂方法的特例,我会通过这篇文章对简单工厂和工厂方法进行讲述。

本文UML类图链接为:https://www.processon.com/view/link/6080def6079129456d4beecf

本文代码链接为:https://github.com/shidawuhen/asap/blob/master/controller/design/7factory.go

1.定义

1.1 工厂方法

工厂方法:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
工厂方法UML类图
image.png
简单工厂UML类图:
image.png

1.2工厂方法分析

通过定义和类图,我们可以做出以下分析:
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

  1. 标准的工厂方法需要有一个工厂类接口,就是UML中的Creator。这个接口的作用是为了实现里氏替换原则:子类对象能够替换程序中父类对象出现的任何地方。这个接口的存在能够保证代码的优雅。
  2. UML中还有一个Product接口,这个接口的存在有三重含义
    • 表明工厂方法创建的工厂,其功能应该是类似的,可以抽象出一个父类
    • 符合里氏替换原则,使代码优雅
    • 符合依赖倒转原则:高层模块不要依赖低层模块。高层模块和低层模块应该通过抽象来互相依赖。而Creator和Product正是依赖关系
  3. 简单工厂和工厂方法相比,缺一个Creator接口,只有一个真实的工厂类。这导致两者在实现和场景上都有区别。
    • 实现上:简单工厂直接返回ConcreteProduct;工厂方法先返回指定工厂,然后通过指定工厂创建ConcreteProduct
    • 场景上:使用简单工厂还是工厂方法标准只有一条,创建Product是否复杂,复杂就用工厂方法,如果只需new,建议使用简单工厂

对于设计原则,大家可以看我的这篇文章:Go设计模式(3)-设计原则

2.使用场景

工厂模式一般用于对于不同的场景,需要创建不同的对象,但是这些对象实现的功能是很相似的,可以抽象出一个父类,对于这种情形就可以使用工厂模式。
在实际中,很多框架都支持多种配置文件,项目启动时解析配置文件,将文件内容写入到内存中。配置文件格式很多,有xml、json、yaml等,这个时候就需要根据后缀来解析文件,使用工厂模式就很合理。

3.代码实现

假设你设计的框架支持xml、yaml格式,对于解析部分的代码我们分别使用简单工厂和工厂方法来进行实现。

  1. package main
  2. import "fmt"
  3. /**
  4. * @Description: 解析接口,有解析函数用来解析文件
  5. */
  6. type ConfigParse interface {
  7. Parse(path string) string
  8. }
  9. /**
  10. * @Description: Json解析类,实现解析接口
  11. */
  12. type JsonConfigParse struct {
  13. }
  14. /**
  15. * @Description: 用于解析Json文件
  16. * @receiver json
  17. * @param path
  18. * @return string
  19. */
  20. func (json *JsonConfigParse) Parse(path string) string {
  21. return "解析json配置文件,路径为:" + path
  22. }
  23. /**
  24. * @Description: Yaml解析类,实现解析接口
  25. */
  26. type YamlConfigParse struct {
  27. }
  28. /**
  29. * @Description: 用于解析Yaml文件
  30. * @receiver yaml
  31. * @param path
  32. * @return string
  33. */
  34. func (yaml *YamlConfigParse) Parse(path string) string {
  35. return "解析yaml配置文件,路径为:" + path
  36. }
  37. /**
  38. * @Description: 简单工厂
  39. */
  40. type SimpleParseFactory struct {
  41. }
  42. func (simple *SimpleParseFactory) create(ext string) ConfigParse {
  43. switch ext {
  44. case "json":
  45. return &JsonConfigParse{}
  46. case "yaml":
  47. return &YamlConfigParse{}
  48. }
  49. return nil
  50. }
  51. /**
  52. * @Description: 工厂方法
  53. */
  54. type NormalParseFactory interface {
  55. createParse() ConfigParse
  56. }
  57. /**
  58. * @Description: Json工厂方法
  59. */
  60. type JsonNormalParseFactory struct {
  61. }
  62. /**
  63. * @Description: 该方法用于创建Json解析器
  64. * @receiver jsonFactory
  65. * @param ext
  66. * @return ConfigParse
  67. */
  68. func (jsonFactory *JsonNormalParseFactory) createParse() ConfigParse {
  69. //此处假装有各种组装
  70. return &JsonConfigParse{}
  71. }
  72. /**
  73. * @Description: Yaml解析工厂
  74. */
  75. type YamlNormalParseFactory struct {
  76. }
  77. /**
  78. * @Description: 该方法用于创建Yaml解析器
  79. * @receiver yamlFactory
  80. * @return ConfigParse
  81. */
  82. func (yamlFactory *YamlNormalParseFactory) createParse() ConfigParse {
  83. //此处假装有各种组装
  84. return &YamlConfigParse{}
  85. }
  86. /**
  87. * @Description: 根据后缀创建工厂
  88. * @param ext
  89. * @return NormalParseFactory
  90. */
  91. func createFactory(ext string) NormalParseFactory {
  92. switch ext {
  93. case "json":
  94. return &JsonNormalParseFactory{}
  95. case "yaml":
  96. return &YamlNormalParseFactory{}
  97. }
  98. return nil
  99. }
  100. func main() {
  101. //简单工厂使用代码
  102. fmt.Println("------------简单工厂")
  103. simpleParseFactory := &SimpleParseFactory{}
  104. parse := simpleParseFactory.create("json")
  105. if parse != nil {
  106. data := parse.Parse("conf/config.json")
  107. fmt.Println(data)
  108. }
  109. //工厂使用代码
  110. fmt.Println("------------工厂方法")
  111. factory := createFactory("yaml")
  112. parse = factory.createParse()
  113. if parse != nil {
  114. data := parse.Parse("conf/config.yaml")
  115. fmt.Println(data)
  116. }
  117. }




返回为:
➜ 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/