Go设计模式(7)-工厂模式

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

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

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

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

1.定义

1.1 工厂方法

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

工厂方法UML类图

image-20210424152500854.png

简单工厂UML类图:

image-20210424183843751.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格式,对于解析部分的代码我们分别使用简单工厂和工厂方法来进行实现。

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/

往期文章回顾:

招聘

  1. 字节跳动|飞书大客户产品经理内推咯
  2. 字节跳动|抖音电商服务端技术岗位虚位以待
  3. 字节跳动招聘专题

设计模式

  1. Go设计模式(7)-工厂模式
  2. Go设计模式(6)-单例模式
  3. Go设计模式(5)-类图符号表示法
  4. Go设计模式(4)-代码编写优化
  5. Go设计模式(4)-代码编写
  6. Go设计模式(3)-设计原则
  7. Go设计模式(2)-面向对象分析与设计
  8. Go设计模式(1)-语法

语言

  1. Go工具之generate
  2. Go单例实现方案
  3. Go通道实现原理
  4. Go定时器实现原理
  5. Beego框架使用
  6. Golang源码BUG追查
  7. Gin框架简洁版
  8. Gin源码剖析

架构

  1. 支付接入常规问题
  2. 限流实现2
  3. 秒杀系统
  4. 分布式系统与一致性协议
  5. 微服务之服务框架和注册中心
  6. 浅谈微服务
  7. 限流实现1
  8. CDN请求过程详解
  9. 常用缓存技巧
  10. 如何高效对接第三方支付
  11. 算法总结

存储

  1. MySQL开发规范
  2. Redis实现分布式锁
  3. 事务原子性、一致性、持久性的实现原理
  4. InnoDB锁与事务简析

网络

  1. HTTP2.0基础教程
  2. HTTPS配置实战
  3. HTTPS连接过程
  4. TCP性能优化

工具

  1. GoLand实用技巧
  2. 根据mysql表自动生成go struct
  3. Markdown编辑器推荐-typora

读书笔记

  1. 原则
  2. 资治通鉴
  3. 敏捷革命
  4. 如何锻炼自己的记忆力
  5. 简单的逻辑学-读后感
  6. 热风-读后感
  7. 论语-读后感
  8. 孙子兵法-读后感

思考

  1. 为动员一切力量争取胜利而斗争
  2. 反对自由主义
  3. 实践论
  4. 评价自己的标准
  5. 服务端团队假期值班方案
  6. 项目流程管理
  7. 对项目管理的一些看法
  8. 对产品经理的一些思考
  9. 关于程序员职业发展的思考
  10. 关于代码review的思考
相关文章
|
4天前
|
设计模式 Go 网络安全
[设计模式 Go实现] 结构型~代理模式
[设计模式 Go实现] 结构型~代理模式
|
4天前
|
设计模式 Go
[设计模式 Go实现] 结构型~享元模式
[设计模式 Go实现] 结构型~享元模式
|
4天前
|
设计模式 Go API
[设计模式 Go实现] 结构型~外观模式
[设计模式 Go实现] 结构型~外观模式
|
4天前
|
设计模式 Go
[设计模式 Go实现] 结构型~组合模式
[设计模式 Go实现] 结构型~组合模式
|
4天前
|
设计模式 Go
[设计模式 Go实现] 结构型~装饰模式
[设计模式 Go实现] 结构型~装饰模式
|
4天前
|
设计模式 Go
[设计模式 Go实现] 结构型~适配器模式
[设计模式 Go实现] 结构型~适配器模式
|
4天前
|
设计模式 Go
[设计模式 Go实现] 行为型~解释器模式
[设计模式 Go实现] 行为型~解释器模式
|
2天前
|
Ubuntu Unix Linux
【GO基础】1. Go语言环境搭建
【GO基础】1. Go语言环境搭建
|
3天前
|
JSON 前端开发 Go
lucky - go 语言实现的快速开发平台
go 语言实现的快速开发平台,自动生成crud代码,前端页面通过json配置,无需编写前端代码。
9 0
|
4天前
|
存储 Java Go
Go 语言切片如何扩容?(全面解析原理和过程)
Go 语言切片如何扩容?(全面解析原理和过程)
13 2