Go语言自带设计模式

小白debug 2023-03-21 08:00

概述

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

和传统的 GOFJavaC# 教科书式的 设计模式 不同,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 stringerror {
    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() (interror){
    return 0nil
}

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/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F_(%E8%AE%A1%E7%AE%97%E6%9C%BA)
[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


评论 (0)
  • 文/郭楚妤编辑/cc孙聪颖‍相较于一众措辞谨慎、毫无掌舵者个人风格的上市公司财报,利亚德的财报显得尤为另类。利亚德光电集团成立于1995年,是一家以LED显示、液晶显示产品设计、生产、销售及服务为主业的高新技术企业。自2016年年报起,无论业绩优劣,董事长李军每年都会在财报末尾附上一首七言打油诗,抒发其对公司当年业绩的感悟。从“三年翻番顺大势”“智能显示我第一”“披荆斩棘幸从容”等词句中,不难窥见李军的雄心壮志。2012年,利亚德(300296.SZ)在深交所创业板上市。成立以来,该公司在细分领
    华尔街科技眼 2025-05-07 19:25 129浏览
  • 后摄像头是长这个样子,如下图。5孔(D-,D+,5V,12V,GND),说的是连接线的个数,如下图。4LED,+12V驱动4颗LED灯珠,给摄像头补光用的,如下图。打开后盖,发现里面有透明白胶(防水)和白色硬胶(固定),用合适的工具,清理其中的胶状物。BOT层,AN3860,Panasonic Semiconductor (松下电器)制造的,Cylinder Motor Driver IC for Video Camera,如下图。TOP层,感光芯片和广角聚焦镜头组合,如下图。感光芯片,看着是玻
    liweicheng 2025-05-07 23:55 75浏览
  • 这款无线入耳式蓝牙耳机是长这个样子的,如下图。侧面特写,如下图。充电接口来个特写,用的是卡座卡在PCB板子上的,上下夹紧PCB的正负极,如下图。撬开耳机喇叭盖子,如下图。精致的喇叭(HY),如下图。喇叭是由电学产生声学的,具体结构如下图。电池包(AFS 451012  21 12),用黄色耐高温胶带进行包裹(安规需求),加强隔离绝缘的,如下图。451012是电池包的型号,聚合物锂电池+3.7V 35mAh,详细如下图。电路板是怎么拿出来的呢,剪断喇叭和电池包的连接线,底部抽出PCB板子
    liweicheng 2025-05-06 22:58 289浏览
  • 随着智能驾驶时代到来,汽车正转变为移动计算平台。车载AI技术对存储器提出新挑战:既要高性能,又需低功耗和车规级可靠性。贞光科技代理的紫光国芯车规级LPDDR4存储器,以其卓越性能成为国产芯片产业链中的关键一环,为智能汽车提供坚实的"记忆力"支持。作为官方授权代理商,贞光科技通过专业技术团队和完善供应链,让这款国产存储器更好地服务国内汽车厂商。本文将探讨车载AI算力需求现状及贞光科技如何通过紫光国芯LPDDR4产品满足市场需求。 车载AI算力需求激增的背景与挑战智能驾驶推动算力需求爆发式
    贞光科技 2025-05-07 16:54 141浏览
  • 2024年初,OpenAI公布的Sora AI视频生成模型,震撼了国产大模型行业。随后国产厂商集体发力视频大模型,快手发布视频生成大模型可灵,字节跳动发布豆包视频生成模型,正式打响了国内AI视频生成领域第一枪。众多企业匆忙入局,只为在这片新兴市场中抢占先机,却往往忽视了技术成熟度与应用规范的打磨。以社交平台上泛滥的 AI 伪造视频为例,全红婵家人被恶意仿冒博流量卖货,明星们也纷纷中招,刘晓庆、张馨予等均曾反馈有人在视频号上通过AI生成视频假冒她。这些伪造视频不仅严重侵犯他人权
    用户1742991715177 2025-05-05 23:08 79浏览
  • 二位半 5线数码管的驱动方法这个2位半的7段数码管只用5个管脚驱动。如果用常规的7段+共阳/阴则需要用10个管脚。如果把每个段看成独立的灯。5个管脚来点亮,任选其中一个作为COM端时,另外4条线可以单独各控制一个灯。所以实际上最多能驱动5*4 = 20个段。但是这里会有一个小问题。如果想点亮B1,可以让第3条线(P3)置高,P4 置低,其它阳极连P3的灯对应阴极P2 P1都应置高,此时会发现C1也会点亮。实际操作时,可以把COM端线P3设置为PP输出,其它线为OD输出。就可以单独控制了。实际的驱
    southcreek 2025-05-07 15:06 196浏览
  • Matter协议是一个由Amazon Alexa、Apple HomeKit、Google Home和Samsung SmartThings等全球科技巨头与CSA联盟共同制定的开放性标准,它就像一份“共生契约”,能让原本相互独立的家居生态在应用层上握手共存,同时它并非另起炉灶,而是以IP(互联网协议)为基础框架,将不同通信协议下的家居设备统一到同一套“语义规则”之下。作为应用层上的互通标准,Matter协议正在重新定义智能家居行业的运行逻辑,它不仅能向下屏蔽家居设备制造商的生态和系统,让设备、平
    华普微HOPERF 2025-05-08 11:40 48浏览
  • ‌一、高斯计的正确选择‌1、‌明确测量需求‌‌磁场类型‌:区分直流或交流磁场,选择对应仪器(如交流高斯计需支持交变磁场测量)。‌量程范围‌:根据被测磁场强度选择覆盖范围,例如地球磁场(0.3–0.5 G)或工业磁体(数百至数千高斯)。‌精度与分辨率‌:高精度场景(如科研)需选择误差低于1%的仪器,分辨率需匹配微小磁场变化检测需求。2、‌仪器类型选择‌‌手持式‌:便携性强,适合现场快速检测;‌台式‌:精度更高,适用于实验室或工业环境。‌探头类型‌:‌横向/轴向探头‌:根据磁场方向选择,轴向探头适合
    锦正茂科技 2025-05-06 11:36 381浏览
  • 多功能电锅长什么样子,主视图如下图所示。侧视图如下图所示。型号JZ-18A,额定功率600W,额定电压220V,产自潮州市潮安区彩塘镇精致电子配件厂,铭牌如下图所示。有两颗螺丝固定底盖,找到合适的工具,拆开底盖如下图所示。可见和大部分市场的加热锅一样的工作原理,手绘原理图,根据原理图进一步理解和分析。F1为保险,250V/10A,185℃,CPGXLD 250V10A TF185℃ RY 是一款温度保险丝,额定电压是250V,额定电流是10A,动作温度是185℃。CPGXLD是温度保险丝电器元件
    liweicheng 2025-05-05 18:36 257浏览
  • UNISOC Miracle Gaming奇迹手游引擎亮点:• 高帧稳帧:支持《王者荣耀》等主流手游90帧高画质模式,连续丢帧率最高降低85%;• 丝滑操控:游戏冷启动速度提升50%,《和平精英》开镜开枪操作延迟降低80%;• 极速网络:专属游戏网络引擎,使《王者荣耀》平均延迟降低80%;• 智感语音:与腾讯GVoice联合,弱网环境仍能保持清晰通话;• 超高画质:游戏画质增强、超级HDR画质、游戏超分技术,优化游戏视效。全球手游市场规模日益壮大,游戏玩家对极致体验的追求愈发苛刻。紫光展锐全新U
    紫光展锐 2025-05-07 17:07 186浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦