首页>国内 > 正文

聊聊 Go 语言自带设计模式

2023-03-21 08:09:24来源:洋芋编程

本文转载自微信公众号「洋芋编程」,作者蛮荆 。转载本文请联系洋芋编程公众号。

在软件工程中,设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。 -- 维基百科


(资料图片仅供参考)

和传统的GOF,Java,C#教科书式的设计模式不同,Go 语言设计从一开始就力求简洁,有其他编程语言基础的读者在学习和使用 Go 语言时, 万万不可按图索骥、生搬硬套,简单的事情复杂化。

本文带领大家一起看一下,Go 语言标准库中自带的编程设计模式。

单例模式

确保一个类只有一个实例,并提供对该实例的全局访问。

通过使用标准库中的sync.Once对业务对象进行简单封装,即可实现单例模式,简单安全高效。

package mainimport "sync"var (    once     sync.Once    instance Singleton)// Singleton 业务对象type Singleton struct {}// NewInstance 单例模式方法func NewInstance() Singleton {    once.Do(func() {        instance = Singleton{}    })    return instance}func main() {    // 调用方代码    s1 := NewInstance()    s2 := NewInstance()    s3 := NewInstance() }

Go 标准库单例模式

简单工厂模式

Go 语言本身没有构造方法特性,工程实践中一般使用NewXXX创建新的对象 (XXX 为对象名称),比如标准库中的:

// errors/errors.gofunc New(text string) error {    return &errorString{text}}// sync/cond.gofunc NewCond(l Locker) *Cond {    return &Cond{L: l}}

在这个基础上,如果方法返回的是interface的时候,其实就等于是简单工厂模式,然后再加一层抽象的话,就接近于抽象工厂模式。

package main// ConfigParser 配置解析接口type ConfigParser interface {    Parse(p []byte)}// JsonParser Json 文件解析器type JsonParser struct {}func (j *JsonParser) Parse(p []byte) {}func newJsonParser() *JsonParser {    return &JsonParser{}}// YamlParser Yaml 文件解析器type YamlParser struct {}func (y *YamlParser) Parse(p []byte) {}func newYamlParser() *YamlParser {    return &YamlParser{}}type ConfigType uint8const (    JsonType ConfigType = 1 << iota    YamlType)// NewConfig 根据不同的类型创建对应的解析器func NewConfig(t ConfigType) ConfigParser {    switch t {    case JsonType:        return newJsonParser()    case YamlType:        return newYamlParser()    default:        return nil    }}func main() {    // 调用方代码    jsonParser := NewConfig(JsonType)    yamlParser := NewConfig(YamlType)}

Go 实现简单工厂模式

对象池模式

通过回收利用对象避免获取和释放资源所需的昂贵成本,我们可以直接使用sync.Pool对象来实现功能。

package mainimport (    "net/http"    "sync")var (    // HTTP Request 对象池    reqPool = sync.Pool{        New: func() any {            return http.Request{}        },    })func main() {    // 调用方代码    r1 := reqPool.Get()    r2 := reqPool.Get()    r3 := reqPool.Get()    reqPool.Put(r1)    reqPool.Put(r2)    reqPool.Put(r3)}

构建模式 (Builder)

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

如果用传统的方法实现构建模式,对应的 Go 语言代码大致是下面这个样子:

package maintype QueryBuilder interface {    Select(table string, columns []string) QueryBuilder    Where(conditions ...string) QueryBuilder    GetRawSQL() string}type MySQLQueryBuilder struct {}func (m *MySQLQueryBuilder) Select(table string, columns ...string) QueryBuilder {    // 具体实现代码跳过    return nil}func (m *MySQLQueryBuilder) Where(conditions ...string) QueryBuilder {    // 具体实现代码跳过    return nil}func (m *MySQLQueryBuilder) GetRawSQL() string {    // 具体实现代码跳过    return ""}func main() {    // 调用方代码    m := &MySQLQueryBuilder{}    sql := m.Select("users", "username", "password").        Where("id = 100").        GetRawSQL()    println(sql)}

Go 实现构建模式

上面的代码中,通过经典的链式调用来构造出具体的 SQL 语句,但是在 Go 语言中,我们一般使用另外一种模式来实现同样的功能FUNCTIONAL OPTIONS, 这似乎也是 Go 语言中最流行的模式之一。

package maintype SQL struct {    Table   string    Columns []string    Where   []string}type Option func(s *SQL)func Table(t string) Option {    // 注意返回值类型    return func(s *SQL) {        s.Table = t    }}func Columns(cs ...string) Option {    // 注意返回值类型    return func(s *SQL) {        s.Columns = cs    }}func Where(conditions ...string) Option {    // 注意返回值类型    return func(s *SQL) {        s.Where = conditions    }}func NewSQL(options ...Option) *SQL {    sql := &SQL{}    for _, option := range options {        option(sql)    }    return sql}func main() {    // 调用方代码    sql := NewSQL(Table("users"),        Columns("username", "password"),        Where("id = 100"),    )    println(sql)}

Go FUNCTIONAL OPTIONS 模式

观察者模式

在对象间定义一个一对多的联系性,由此当一个对象改变了状态,所有其他相关的对象会被通知并且自动刷新。

如果用传统的方法实现观察者模式,对应的 Go 语言代码大致是下面这个样子:

package mainimport "math"// Observer 观察者接口type Observer interface {    OnNotify(Event)}// Notifier 订阅接口type Notifier interface {    Register(Observer)    Deregister(Observer)    Notify(Event)}type (    Event struct {        Data int64    }    eventObserver struct {        id int    }    eventNotifier struct {        observers map[Observer]struct{}    })// OnNotify 观察者收到订阅的时间回调func (o *eventObserver) OnNotify(e Event) {}// Register 注册观察者func (o *eventNotifier) Register(l Observer) {    o.observers[l] = struct{}{}}// Deregister 移除观察者func (o *eventNotifier) Deregister(l Observer) {    delete(o.observers, l)}// Notify 发出通知func (o *eventNotifier) Notify(e Event) {    for p := range o.observers {        p.OnNotify(e)    }}func main() {    // 调用方代码    notifier := eventNotifier{        observers: make(map[Observer]struct{}),    }    notifier.Register(&eventObserver{1})    notifier.Register(&eventObserver{2})    notifier.Register(&eventObserver{3})    notifier.Notify(Event{Data: math.MaxInt64})}

Go 实现观察者模式

但其实我们有更简洁的方法,直接使用标准库中的sync.Cond对象,改造之后的观察者模式代码大概是这个样子:

package mainimport (    "fmt"    "sync"    "time")var done = falsefunc read(name string, c *sync.Cond) {    fmt.Println(name, "starts reading")    c.L.Lock()    for !done {        c.Wait() // 等待发出通知    }    c.L.Unlock()}func write(name string, c *sync.Cond) {    fmt.Println(name, "starts writing")    time.Sleep(100 * time.Millisecond)    c.L.Lock()    done = true // 设置条件变量    c.L.Unlock()    fmt.Println(name, "wakes all")    c.Broadcast() // 通知所有观察者}func main() {    cond := sync.NewCond(&sync.Mutex{}) // 创建时传入一个互斥锁    // 3 个观察者    go read("reader1", cond)    go read("reader2", cond)    go read("reader3", cond)    time.Sleep(time.Second) // 模拟延时    write("writer-1", cond) // 发出通知    time.Sleep(time.Second) // 模拟延时}

Go 标准库观察者模式

将代码改造为sync.Cond之后,代码量更好,结构更简洁。

ok/error 模式

在 Go 语言中,经常在一个表达式返回2个参数时使用这种模式:

第 1 个参数是一个值或者nil第 2 个参数是true/false或者error

在一个需要赋值的if条件语句中,使用这种模式去检测第 2 个参数值会让代码显得优雅简洁。

在函数返回时检测错误

package mainfunc foo() (int, error){    return 0, nil}func main() {    if v, err := foo(); err != nil {        panic(err)    } else {        println(v)    }}

检测 map 是否存在指定的 key

package mainfunc main() {    m := make(map[int]string)    if v, ok := m[0]; ok {        println(v)    }}

类型断言

package mainfunc foo() interface{} {    return 1024}func main() {    n := foo()    if v, ok := n.(int); ok {        println(v)    }}

检测通道是否关闭

package mainfunc main() {    ch := make(chan int)    go func() {        for i := 0; i < 5; i++ {            ch <- i        }        close(ch)    }()    for {        if v, ok := <-ch; ok {            println(v)        } else {            return        }    }}// $ go run main.go// 输出如下// 0// 1// 2// 3// 4

附加内容闭包

有时候,我们可以利用闭包实现一些短小精悍的内部函数。

计数器

package mainfunc main() {    newSeqInc := func() func() int {        seq := 0        return func() int {            seq++            return seq        }    }    seq := newSeqInc() // 创建一个计数器    println(seq())     // 1    println(seq())     // 2    println(seq())     // 3    seq2 := newSeqInc() // 创建另一个计数器    println(seq2())     // 1    println(seq2())     // 2    println(seq2())     // 3}

小结

下面表格列出了常用的设计模式,其中 Go 标准库自带的模式已经用删除线标识,读者可以和自己常用的设计模式进行对比。

创建型模式

结构性模式

行为型模式

单例

适配器

策略

简单工厂

装饰者

观察者

抽象工厂

代理

状态

对象池

责任链

构建

长期以来,设计模式一直处于尴尬的位置:初学者被各种概念和关系搞得不知所云,有经验的程序员会觉得 “这种代码写法 (这里指设计模式),我早就知道了啊”。 鉴于这种情况,本文中没有涉及到的设计模式,笔者不打算再一一描述,感兴趣的读者可以直接跳到仓库代码[1]查看示例代码。

相比于设计模式,更重要的是理解语言本身的特性以及最佳实践。

扩展阅读Go 与面向对象设计模式 - 维基百科[2]go-examples-for-beginners/patterns[3]圣杯与银弹 · 没用的设计模式[4]tmrts/go-patterns[5]DESIGN PATTERNS in GO[6]解密“设计模式”[7]Go 编程模式 - 酷壳[8]引用链接

[1]仓库代码:https://github.com/duanbiaowu/go-examples-for-beginners/tree/master/patterns

[2]设计模式 - 维基百科:https://zh.wikipedia.org/wiki/设计模式_(计算机)

[3]​go-examples-for-beginners/patterns:​​https://github.com/duanbiaowu/go-examples-for-beginners/tree/master/patterns​​

[4]圣杯与银弹 · 没用的设计模式:https://draveness.me/holy-grail-design-pattern/

[5]​tmrts/go-patterns:​​https://github.com/tmrts/go-patterns​​

[6]DESIGN PATTERNS in GO:https://refactoring.guru/design-patterns/go

[7]解密“设计模式”:​​http://www.yinwang.org/blog-cn/2013/03/07/design-patterns​​

[8]Go 编程模式 - 酷壳:https://coolshell.cn/articles/series/go%e7%bc%96%e7%a8%8b%e6%a8%a1%e5%bc%8f

关键词:

相关新闻

Copyright 2015-2020   三好网  版权所有 联系邮箱:435 22 640@qq.com  备案号: 京ICP备2022022245号-21