Go设计模式-工厂模式

Posted 程序员麻辣烫

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go设计模式-工厂模式相关的知识,希望对你有一定的参考价值。

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

本文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工厂方法分析

通过定义和类图,我们可以做出以下分析:

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

  1. 标准的工厂方法需要有一个工厂类接口,就是UML中的Creator。这个接口的作用是为了实现里氏替换原则:子类对象能够替换程序中父类对象出现的任何地方。这个接口的存在能够保证代码的优雅。

  2. UML中还有一个Product接口,这个接口的存在有三重含义

    • 表明工厂方法创建的工厂,其功能应该是类似的,可以抽象出一个父类

    • 符合里氏替换原则,使代码优雅

    • 符合依赖倒转原则:高层模块不要依赖低层模块。高层模块和低层模块应该通过抽象来互相依赖。而Creator和Product正是依赖关系

  3. 简单工厂和工厂方法相比,缺一个Creator接口,只有一个真实的工厂类。这导致两者在实现和场景上都有区别。

    • 实现上:简单工厂直接返回ConcreteProduct;工厂方法先返回指定工厂,然后通过指定工厂创建ConcreteProduct

    • 场景上:使用简单工厂还是工厂方法标准只有一条,创建Product是否复杂,复杂就用工厂方法,如果只需new,建议使用简单工厂

对于设计原则,大家可以看我的这篇文章:

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/

往期文章回顾:

招聘

设计模式

语言

架构

存储

网络

工具

读书笔记

思考


以上是关于Go设计模式-工厂模式的主要内容,如果未能解决你的问题,请参考以下文章

[设计模式C++go]简单工厂模式

[设计模式C++go]简单工厂模式

[03]Go设计模式:工厂模式(Factory Pattern)

Go设计模式-工厂模式

[设计模式C++go]创建型模式:工厂模式

[设计模式C++go]创建型模式:工厂模式