大家好,我是追麾(hui)。
接下来的几周时间给大家分享一系列Go设计模式文章,设计模式在我们的面试中也会被经常问到,像Java语言会用到设计模式,对于Go语言,设计模式使用会比较弱点,所以这里给大家一起来学习分享Go的设计模式,让我们在开发中也大量应用到设计模式,帮助我们的Go代码更加优美,可读性更好。
第一篇主要分享两种模式,单例模式和工厂方法模式。
单例模式定义:Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。)
单例模式优缺点
单例模式的应用场景
单例模式实现方式
懒汉式:使用的时候才进行初始化,即懒加载
package main
func main() {
// 调用实例对象
GetInstance()
}
type Instance struct {
}
// 实例全局对象
var lazyInstance *Instance
func GetInstance() *Instance {
// 判断第一次如果为空,则给lazy对象重新赋值。
if lazyInstance == nil {
lazyInstance = &Instance{}
}
return lazyInstance
}
在Go语言中,通过懒汉式来实现单例,重要点在判断第一次实例化的对象为空,则给对象重新赋值返回,这里如果每次调用GetInstance,出现并发问题,则会造成不少开销,像这种问题主要是使用双重检测来解决。
饿汉式:在程序初始化的时候或者类加载的时候就已经创建好对象,加载速度快。
我们通过Go示例来看下饿汉式实现:
package main
func main() {
GetInstance()
}
//对象
type Instance struct {
}
var lazyInstance *Instance
// 饿汉式:在程序初始化的时候或者类加载的时候就已经创建好对象,加载速度快。
func init() {
lazyInstance = &Instance{}
}
// 饿汉式调用
func GetInstance() *Instance {
return lazyInstance
}
在Go语言中,通过饿汉式来实现单例主要依赖init函数初始化变量,或者自己定义函数(函数内部初始化变量)在初始化程序或者加载包之前初始化,然后定义一个通用的方法对外服务。
双重校验:在懒汉式的基础上,加上类级别的锁。在Go语言中主要是基于package做全局锁。
我们通过Go示例来看下双重校验实现:
package main
import "sync"
func main() {
// 调用实例对象
GetInstance()
}
type Instance struct {
}
// 实例全局对象
var lazyInstance *Instance
// 包级别的锁
var once = &sync.Once{}
func GetInstance() *Instance {
// 判断第一次如果为空,则给lazy对象重新赋值。
if lazyInstance == nil {
// once.Do只执行一次
once.Do(func() {
lazyInstance = &Instance{}
})
}
return lazyInstance
}
静态变量(Go里面通过常量来实现):Java中利用了静态内部类延迟初始化的特性,来达到与双重校验锁方式一样的功能,像Go中只能通过常量来实现,但是Go的常量仅支持整型,浮点型,字符串,bool值,所以在Go中实现常量实现单例模式没什么意义。
枚举类:该方式利用了枚举类的特性,不仅能避免线程同步问题,还防止反序列化重新创建新的对象。在Java中是能实现,但是Go中是没有这种,无法实现单例。
工厂方法模式定义:工厂方法(Factory Method)类定义产品对象创建接口,但由子类实现具体产品对象的创建。
工厂方法模式优缺点
工厂方法模式应用场景
工厂方法模式实现方式
在Go语言中,需要实现工厂方法模式,则需要使用到接口,定义一些公共的方法,用不同的struct对象来实现接口里面公共的方法,struct本身的对象的具体类型是不知道的,只有对象本身才知道。下面我们具体来看一个示例。
在很多聚合广告业务中,我们需要调用不同的广告厂商的接口,然后通过不同的广告厂商的数据存到自己的业务系统中,这个时候就可以使用工厂方法模式来实现,首先一些公共的方法RequestThirdApi(),每个厂商都要实现这些方法,每个厂商本身需要先按照自身的广告厂商接口组装请求数据,然后去请求,请求完了之后不同厂商接口返回的结构是不一样的,结果不一样,则可以通过处理成同样结构的数据到我们自己的系统。下面我们来看下具体实现代码:
package main
import "fmt"
func main() {
NewFactory("kuaishou").RequestThirdApi()
}
// 做广告投放需要拉取不同的广告厂商,不同的广告厂商是不一样,但是都会存在请求第三方的方法
type ThirdResponseData struct {
}
// 工厂方法接口
type HttpRequestFactory interface {
RequestThirdApi() *ThirdResponseData
}
// 头条厂商 工厂类
type TouTiaoFactory struct {
}
func (t *TouTiaoFactory) RequestThirdApi() *ThirdResponseData {
// 组装请求
// 执行请求
// 处理请求响应结果,处理成ThirdResponseData这个的返回结构
fmt.Println("头条")
return &ThirdResponseData{}
}
// 快手厂商 工厂类
type KuaiShouFactory struct {
}
func (t *KuaiShouFactory) RequestThirdApi() *ThirdResponseData {
// 组装请求
// 执行请求
// 处理请求响应结果,处理成ThirdResponseData这个的返回结构
fmt.Println("快手")
return &ThirdResponseData{}
}
// 用一个简单工厂封装工厂方法,这个是入口函数
func NewFactory(f string) HttpRequestFactory {
switch f {
case "kuaishou":
return &KuaiShouFactory{}
case "toutiao":
return &TouTiaoFactory{}
}
return nil
}
上面只是一个简单的版本,实际比这个复杂,我们会处理组装请求,执行请求,响应都会用不同的方法来实现,这样会看起来比较清晰。
关于Go的设计模式第一篇,就先分享到这里。
参考文献
《软件设计模式之禅》