您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > Go语言

Go 语言自带设计模式

时间:2023-03-21 11:20:14  来源:微信公众号  作者:洋芋编程

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

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

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

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

单例模式

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

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

package main

import "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.go

func New(text string) error {
    return &errorString{text}
}

// sync/cond.go
func 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 uint8

const (
    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 main

import (
    ".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 main

type 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 main

type 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 main

import "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 main

import (
    "fmt"
    "sync"
    "time"
)

var done = false

func 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 main

func foo() (int, error){
    return 0, nil
}

func main() {
    if v, err := foo(); err != nil {
        panic(err)
    } else {
        println(v)
    }
}

 

检测 map 是否存在指定的 key

 

package main

func main() {
    m := make(map[int]string)

    if v, ok := m[0]; ok {
        println(v)
    }
}

 

类型断言

 

package main

func foo() interface{} {
    return 1024
}

func main() {
    n := foo()
    if v, ok := n.(int); ok {
        println(v)
    }
}

 

检测通道是否关闭

 

package main

func 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 main

func 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



Tags:Go 语言   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
本文转载自微信公众号「洋芋编程」,作者蛮荆 。转载本文请联系洋芋编程公众号。在软件工程中,设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决...【详细内容】
2023-03-21  Tags: Go 语言  点击:(0)  评论:(0)  加入收藏
本文我们介绍怎么使用命令行工具 micro new 创建一个 gRPC 服务,并且怎么构建和运行服务。​01 介绍Go 开源项目 Micro​ 为我们提供一套微服务解决方案,它主要包含两个部分...【详细内容】
2023-03-06  Tags: Go 语言  点击:(17)  评论:(0)  加入收藏
本文我们介绍了跨平台文件监听库 fsnotify,它主要用于自动监听文件中的内容变更。我们通过 fsnotify 源码和示例代码,介绍了该库支持的功能和使用方式。​1、介绍Go 语言作为...【详细内容】
2023-02-26  Tags: Go 语言  点击:(14)  评论:(0)  加入收藏
本文我们通过在 Gin 构建的应用中,使用 Zap 记录请求日志,介绍了 Zap 的使用方式,最后还通过 lumberjack 日志切割库进行切割日志。​1、介绍我们在之前的文章中介绍过标准库 l...【详细内容】
2023-02-13  Tags: Go 语言  点击:(34)  评论:(0)  加入收藏
Hello,大家好,又见面了!上一遍我们将 channel 相关基础以及使用场景。这一篇,还需要再次进阶理解channel 阻塞问题。以下创建一个chan类型为int,cap 为3。ch := make(chan string...【详细内容】
2022-01-04  Tags: Go 语言  点击:(499)  评论:(0)  加入收藏
作为开发人员,微信生态我们不能无视,微信提供的开放能力,我们应该有所了解。微信支付作为重要的一部分,平时工作中可能难免会遇到。Go 作为一门新语言,微信支付没有提供 Go 的 SD...【详细内容】
2020-09-04  Tags: Go 语言  点击:(154)  评论:(0)  加入收藏
Go语言开发团队花了很长时间来解决当今软件开发人员面对的问题。开发人员在为项目选择语言时,不得不在快速开发和性能之间做出选择。C和C++这类语言提供了很快的执行速度,而Ru...【详细内容】
2020-08-27  Tags: Go 语言  点击:(130)  评论:(0)  加入收藏
在 2 世纪, 发送机密消息的一个有效方法就是对每个字母进行位移, 使得 &#39;a&#39; 变为 &#39;d&#39; , &#39;b&#39; 变为 &#39;e&#39; , 依次类推。 这样处理产生的结果看上去...【详细内容】
2020-08-11  Tags: Go 语言  点击:(174)  评论:(0)  加入收藏
大家好,从今天起,我们一起来学习 Echo 框架。这几年,随着 Go 语言的发展,各种 Web 框架也出现了。常言道:没有选择是一种无奈,有选择是一种痛苦。所以,大家总是问:Web 框架大佬推荐...【详细内容】
2020-06-30  Tags: Go 语言  点击:(245)  评论:(0)  加入收藏
背景介绍直播系统主要是以内容为主,好的内容可以吸引用户来欣赏,也能为公司带来可观的收益,既然有传播的入口,那么必然有负面内容的出现,随着平台用户量不断扩大,内容的监管也是必...【详细内容】
2020-06-02  Tags: Go 语言  点击:(123)  评论:(0)  加入收藏
▌简易百科推荐
本文转载自微信公众号「洋芋编程」,作者蛮荆 。转载本文请联系洋芋编程公众号。在软件工程中,设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决...【详细内容】
2023-03-21  洋芋编程  微信公众号  Tags:Go 语言   点击:(0)  评论:(0)  加入收藏
1、定义结点package mainimport ( "fmt")// 定义结点type BinaryTreeNode struct { Data int Left *BinaryTreeNode Right *BinaryTreeNode}2、创建结点// 创...【详细内容】
2023-03-16  奇幻小鱼k    Tags:Go语言   点击:(7)  评论:(0)  加入收藏
前言本文是根据阳哥 知识星球中的资料 整理的学习笔记,第一章关于Go语言中常见的语法现象。我的思考:一门语言中的语法都是固定的,基础语法几乎都差不多,本篇文章涉及到 Go 入门...【详细内容】
2023-03-14  程序员升级打怪之旅    Tags:Go   点击:(11)  评论:(0)  加入收藏
背景一谈到golang,大家的第一感觉就是高并发,高性能。但是语言本身的优势是不是,就让程序员觉得编写高性能的处理系统变得轻而易举,水到渠成呢。下面这篇文章给大家的提醒便是,...【详细内容】
2023-03-13  研道鸠摩智  今日头条  Tags:Golang   点击:(19)  评论:(0)  加入收藏
在日常开发中,经常会遇到在程序中获取路径的问题。相信很多同学被这个问题搞得头痛不已,可能也没有深入思考过这个问题,在网上搜到相关代码就稀里糊涂得使用了,也没有在不同的...【详细内容】
2023-03-13  路多辛  今日头条  Tags:Golang   点击:(15)  评论:(0)  加入收藏
背景在并发处理中,资源争用是一个常见的问题。为了避免资源争用,需要进行优化。以下是一些可以优化并发处理中的资源争用问题的建议: 避免锁竞争:锁竞争是一种常见的资源争用问...【详细内容】
2023-03-10  研道鸠摩智  今日头条  Tags:Go   点击:(10)  评论:(0)  加入收藏
本文我们介绍怎么使用命令行工具 micro new 创建一个 gRPC 服务,并且怎么构建和运行服务。​01 介绍Go 开源项目 Micro​ 为我们提供一套微服务解决方案,它主要包含两个部分...【详细内容】
2023-03-06  Golang语言开发栈  微信公众号  Tags:Go 语言   点击:(17)  评论:(0)  加入收藏
Go 为了自身 goroutine 执行和调度的效率,自身在 runtime 中实现了一套 goroutine 的调度器。 写在前面Go 为了自身 goroutine 执行和调度的效率,自身在 runtime 中实现了一...【详细内容】
2023-03-03  字节跳动技术团队  微信公众号  Tags:Go   点击:(12)  评论:(0)  加入收藏
MySQL中如何优化LIMIT分页?这个问题我们今天一起来聊一聊。以下是一个示例,演示如何优化MySQL 中limit 分页查询的性能:假设我们有一个名为 users 的表,其中存储了 1,000,000 条...【详细内容】
2023-02-27  数据库干货铺  今日头条  Tags:MySQL   点击:(18)  评论:(0)  加入收藏
本文我们介绍了跨平台文件监听库 fsnotify,它主要用于自动监听文件中的内容变更。我们通过 fsnotify 源码和示例代码,介绍了该库支持的功能和使用方式。​1、介绍Go 语言作为...【详细内容】
2023-02-26  frank    Tags:Go 语言   点击:(14)  评论:(0)  加入收藏
站内最新
站内热门
站内头条