惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

蛮荆

如何获取更多的免费服务器 Kubernetes 调度器队列 - 设计与实现 Kubernetes 调度器 - 核心流程 Kubernetes Networking Model & CNI Kubernetes 控制器管理总结 Kubernetes CronJob 设计与实现 Kubernetes Job 设计与实现 Kubernetes HPA 设计与实现 Kubernetes Deployment 滚动更新实现原理 Kubernetes GC 设计与实现 Kubernetes Pod 驱逐 - 设计与实现 Kubernetes Daemonset 设计与实现 Kubernetes ReplicaSet 设计与实现 Kubernetes EndPoint 设计与实现 Kubernetes Informer 设计与实现 降本增效之应用优化 (三) 日志存储与检索 Kubernetes Pod 设计与实现 - 创建流程 Kubernetes 探针设计与实现 Unix 编程艺术名句摘录 Kubernetes - CRI 概述 Golang 编译速度为什么这么快? Kubernetes Pod 设计与实现 - Pause 容器 Kubernetes - kube-proxy 代理模式工程优化 Kubernetes 应用最佳实践 - 优雅关闭长连接 Kubernetes Service 类型和会话亲和性 Kubernetes 为什么需要 Ingress Kubernetes 架构 - 控制平面和数据平面 降本增效之应用优化 (二) 大报表 Go 语言如何获取 CPU 利用率 降本增效之应用优化 (一) Redis 业务规则引擎演变过程简述 微服务中的熔断算法 漏桶算法和令牌桶算法 jsonparser 为什么比标准库的 encoding/json 快 10 倍 ? zap 高性能设计与实现 HTTP Router 算法演进 布谷鸟过滤器 fastcache 高性能设计与实现 Web 常见的三个安全问题 ants Code Reading 布谷鸟过滤器 Go 线程安全 map 方案选型 布隆过滤器 死锁、活锁、饥饿、自旋锁 sync.Pool Code Reading Go 内存管理概述 Go netpoll Code Reading goroutine 泄漏与检测 time/Timer Code Reading GMP Scheduler Code Reading Go channel 的 15 条规则和底层实现 为什么 Linux “一切皆文件” context.Context Code Reading runtime/HACKING.md Goland 最佳实践 互联网开发与金庸武学 为什么 Redis 6.0 引入多线程模型? Kubernetes 应用最佳实践 - 金丝雀发布 容器中如何正确配置 GOMAXPROCS ? singleflight Code Reading sync.Map Code Reading sync.Cond Code Reading sync.WaitGroup Code Reading sync.RWMutex Code Reading sync.Mutex Code Reading sync.Once Code Reading Go 无锁编程 sync/atomic Code Reading goroutine 交替打印奇偶数 GODEBUG Go 并发模式 Go 汇编 UUID 通用技术选型 Kubernetes 应用最佳实践 - 水平自动伸缩 Go 高性能 Tips fasthttp 为什么比标准库 net/http 快 10 倍 ? 技术文章配图指南 ChatGPT 初体验 Docker 网络原理概览 iptables 的五表五链 Kubernetes 应用最佳实践 - 亲和性和污点容忍度 Go 的反射与三大定律 Docker 官方提供的最佳实践 HTTP1 到 HTTP3 的工程优化 Kubernetes 应用最佳实践 - Sidecar 模式 Kubernetes 应用最佳实践 - init 容器和钩子函数 为什么 recover 必须在 defer 中调用? 为什么 defer 的执行顺序和注册顺序不同? Go map 设计与实现 Go 切片扩容底层实现 Go 语言中的零拷贝 Go Delve 云原生和边缘计算简介 Kubernetes Pod 服务质量等级 Kubernetes 应用最佳实践 - 探针 Kubernetes 应用最佳实践 - 资源请求和限制 CDN 原理 Kubernetes 应用最佳实践 - 开篇 缓存策略和模式 Go 内存模型
Go 语言内置的设计模式
2023-03-22 · via 蛮荆

2023-03-22 设计模式

概述

在软件工程中,设计模式(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 标准库自带的 模式 已经用删除线标识,读者可以和自己常用的 设计模式 进行对比。

创建型模式 结构性模式 行为型模式
单例 适配器 策略
简单工厂 装饰者 观察者
抽象工厂 代理 状态
对象池 责任链
构建

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

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

扩展阅读