当前位置: 首页> 财经> 金融 > 上海闵行区房价_淄博网站设计策划方案公司_市场调研报告模板范文_电商代运营

上海闵行区房价_淄博网站设计策划方案公司_市场调研报告模板范文_电商代运营

时间:2025/7/15 3:01:06来源:https://blog.csdn.net/Shoulen/article/details/142586210 浏览次数:0次
上海闵行区房价_淄博网站设计策划方案公司_市场调研报告模板范文_电商代运营

设计模式就某些编码场景下的最佳实践,用来解决常见的软件设计问题。Go 语言不是面向对象语言,但是可以使用结构体、接口等类型实现面向对象语言的特性,想要弄懂设计模式,要熟练的使用 Go 语言接口类型 和结构体类型

设计模式总体上分为创建型、结构型和行为型 3 类、共 25 种经典设计方案,在 Go 项目开发中常见的有 6 种

下面详细说

创建型

创建型设计模式提供了一种 在创建对象的同时隐藏创建逻辑 的方式,而不是使用 new 运算符直接实例化对象

这种类型的设计模式有单例模式和工厂模式,工厂模式包括简单工厂模式、抽象工厂模式和工厂方法模式三种,这种设计模式在 Go 项目开发种比较常用

单例模式

单例模式是最简单的一个模式。在 Go 中,单例模式指的就是全局只有一个实例,而且只被初始化一次,比较适合 全局共享一个示例,并且只需要被初始化一次 的场景,比如数据库实例、全局配置、全局任务池等

单例模式又分为饿汉方式和懒汉方式,饿汉方式是全局的单例实例在包被加载时创建,懒汉方式是全局的单例实例在第一次被使用时创建

饿汉方式的单例模式:

type singleton struct{}
​
var ins *singleton = &singleton{}
​
func GetInsOr() *singleton {return ins
}

在包被导入时,实例会直接初始化,如果初始化耗时,会导致程序加载时间变长

懒汉方式是开源项目中使用最多的,它的缺点是非并发安全,在实际使用时需要加锁,一个简单的实现:

type singleton struct{}
​
var ins *singleton
​
func GetInsOr() *singleton {if ins == nil {ins = &singleton{}}return ins
}

可以看到,在调用 GetInsOr() 函数时,如果 ins 为 nil,就会创建一个 ins 实例,如果不加锁,就会有多个实例创建

可以对实例加锁,保证并发安全:

import "sync"
​
type singleton struct{}
​
var ins *singleton
var mu sync.Mutex
​
func GetInsOr() *singleton {if ins == nil {mu.Lock()if ins == nil {ins = &singleton{}}mu.Unlock()}return ins
}

要注意加锁后需要再判断是否已经创建好实例。这样就保证了并发安全

除了饿汉方式和懒汉方式,在 Go 开发中还有一种更优雅的实现方式,比较推荐使用:

import "sync"
​
type singleton struct{}
​
var ins *singleton
var once sync.Once
​
func GetInsOr() *singleton {once.Do(func() {ins = &singleton{}})return ins
}

sync.Once 是一个结构体,它提供的 Do 方法可以确保 ins 实例全局只被创建一次,还可以确保在并发场景下,只有一个线程能执行这个函数,Do 方法的参数只能是一个没有参数和返回值的匿名函数,用于做一些初始化操作

工厂模式

工厂模式是面向对象编程中的常用模式,在 Go 中,可以把结构体理解为类,比如:

type Person struct {Name stringAge int
}
​
func (p Person) Greet() {fmt.Println("111")
}

Person 结构体实现了 Greet 方法,有了 Person 结构体,就可以通过简单工厂模式、抽象工厂模式、工厂方法模式这三种方式来创建一个 Person 实例

简单工厂模式是最常用、最简单的,它就是接收一些参数,然后返回 Person 实例:

type Person struct {Name stringAge int
}
​
func (p Person) Greet() {fmt.Println("111")
}
​
func NewPerson(name string, age int) *Person {return &Person {Name: name,Age: age,}
}

p := &Person{} 这种创建方式相比,简单工厂模式可以确保创建的实例具有需要的参数,进而保证实例的方法可以按预期执行,比如通过 NewPerson 方法创建的 Person 实例,可以确保实例的 name 和 age 属性被设置

抽象工厂模式和简单工厂模式的唯一区别,就是返回的是接口而不是结构体

通过返回接口,可以在 不公开内部实现的情况下,让调用者使用提供好的各种功能,比如:

type Person interface {Greet()
}
​
type person struct {name stringage int
}
​
func (p person) Greet() {fmt.Println("111")
}
​
func NewPerson(name string, age int) Person {return person {name: name,age: age,}
}

注意接口名是开头大写的 Person,而结构体是开头小写的 person,在 Go 中,开头小写的结构体是不能被导出的,在上面的例子中,只能通过 NewPerson 函数去生成接口类型的实例,这样就隐藏了 person 结构体的内部实现细节

通过返回接口类型,还可以实现多个工厂函数,来实现返回不同的接口实现:

type Doer interface {Do(req *http.Request) (*http.Response, error)
}
​
func NewHTTPClient() Doer {return &http.Client{}
}
​
// mock 的 HTTP 连接 用于模拟外部连接
type mockHTTPClient struct{}
​
func (*mockHTTPClient) Do(req *http.Request) (*http.Response, error) {// 假设 httptest.NewRecorder 是实现好的方法// 用于返回一个新的 request 实例res := httptest.NewRecorder()return res.Result, nil
}
​
func NewMockHTTPClient() Doer {return &mockHTTPClient{}
}

NewHTTPClientNewMockHTTPClient 都返回了同一个接口类型 Doer,这使得两者可以互相使用,如果想测试一段调用了 Doer 接口的 Do 方法的代码时,就可以使用 Mock 出来的 HTTP 客户端,避免调用外部接口带来的失败,只专注于测试想测试的代码片段

比如现在想测试下面这段代码:

func QueryUser(doer Doer) error {req, err := http.NewRequest("Get", "http://iam.api.marmotedu.com:8080/v1/secrets", nil)if err != nil {return err}_, err := doer.Do(req)if err != nil {return err}// 处理一些其他逻辑// ...return nil
}

给这段代码编写测试用例为:

func TestQueryUser(t *testing.T) {doer := NewMockHTTPClient()if err := QueryUser(doer); err != nil {t.Errof(QueryUser failed, err: %v", err)}
}

这个测试用例忽略了请求外部的 http://iam.api.marmotedu.com:8080/v1/secrets 带来的错误,只专注于核心业务逻辑

另外,在使用简单工厂模式和抽象工厂模式返回实例对象时,都可以返回指针,比如:

简单工厂模式:

return &Person {Name: name,Age: age
}

抽象工厂模式:

return &person {Name: name,Age: age
}

但是在实际开发中,推荐使用非指针的实例,因为使用工厂模式是想通过创建实例,来调用其提供的方法,而不是对实例做更改,如果要对实例进行更改,可以给实例实现 SetXXX 方法,返回非指针的实例,可以避免属性被意外修改

在简单工厂模式中,依赖于唯一的工厂对象,如果需要创建一个实例,就要向工厂中传入一个参数,如果工厂函数要根据传入的参数值返回不同类型的实例,如果要创建一种新的实例,就需要在工厂中修改函数,这会导致耦合度过高,这时候就可以使用 工厂方法模式

在工厂方法模式中,依赖工厂函数,通过工厂函数来创建多种工厂,把实例创建从 由一个对象负责所有具体实例的实例化,变成一群子实例负责对具体实例的实例化,从而将过程解耦

比如:

type Person struct {name stringage int
}
​
func NewPersonFactory(age int) func(name string) Person {return func(name string) Person {return Person {name: name,age: age,}}
}

NewPersonFactory函数返回了一个闭包函数,使用时可以创建具有默认年龄的工厂:

newBaby := NewPersonFactory(1)
baby := newBaby("john")
​
newTeenager := NewPersonFactory(16)
teen := newTeenager("jill")

结构型模式

结构型模式关注 类和对象的组合,这一类型中有策略模式和模板模式

策略模式

策略模式定义了一组算法,将每个算法封装起来,并且使它们之间可以互换

在项目开发中,经常要根据不同的场景,采取不同的措施,也就是不同的策略。比如要对 a、b 这两个整数进行运算,根据条件的不同,需要执行不同的计算方式,就可以把所有操作封装在同一个函数中,通过 if ... else ... 来调用不同的计算方式,这种方式称之为硬编码

在实际应用中,随着功能和体验的不断增长,经常需要增加/修改策略,这样就需要不断修改已有的代码,这不仅会让这个函数越来越难维护,还可能因为修改带来一些 bug,为了解耦,就需要使用策略模式,定义一些独立的类来封装不同的算法,每一个类封装一个具体的算法

比如:

// 策略类
type IStrategy interface {do(int, int) int
}
​
// 策略实现:加
type add struct{}
​
func (*add) do(a, b int) int {return a + b
}
​
// 策略实现:减
type reduce struct{}
​
func (*reduce) do(a, b int) int {return a - b
}
​
// 具体策略的执行者
type Operator struct {strategy IStrategy
}
​
// 设置策略
func (op *Operator) setStrategy(strategy IStrategy) {op.strategy = strategy
}
​
// 调用策略中的方法
func (op *Operator) calculate(a, b int) int {return op.strategy.do(a, b)
}

在这段代码中,定义了策略接口 ISstrategy,还定义了 addreduce 这两种策略,最后定义了一个策略执行者,可以设置不同的策略并执行,比如:

func TestStrategy(t *testing.T) {op := Operator{}// 设置策略为 加op.setStrategy(&add{})result := op.calculate(1, 2)fmt.Println("add:", result)op.setStrategy(&reduce{})result = operator.calculate(2, 1)fmt.Println("reduce:", result)
}

这样就可以随意更换策略,而不影响 Operator 的所有实现

模板模式

模板模式定义一个操作中算法的骨架,将一些步骤延迟到子类中,这种方法可以让子类在不改变一个算法结构的情况下,能重新定义该算法的某些特定步骤

实现上,模板模式将一个类中的公用方法放在抽象类中实现,不能公共使用的方法作为抽象方法,强制子类去实现。这样就做到了将一个类作为一个模板,让开发者去填充需要填充的地方

比如:

type Cooker interface {fire()cooke()outfire()
}
​
// 类似于一个抽象类
type CookMenu struct{}
​
func (CookMenu) fire() {fmt.Println("开火")
}
​
// 做菜,交给具体的子类实现
func (CookMenu) cooke() {
}
​
func (CookMenu) outfire() {fmt.Println("关火")
}
​
// 封装具体步骤
func doCook(cook Cooker) {cook.fire()cook.cooke()cook.outfire()
}
​
type XiHongShi struct {CookMenu
}
func (*XiHongShi) cooke() {fmt.Println("做西红柿")
}
​
type ChaoJiDan struct {CookMenu
}
func (ChaoJiDan) cooke() {fmt.Println("做炒鸡蛋")
}

在上面这段代码中,把通用的开火和关火交给了抽象父类实现,子类通过结构体嵌套的方式继承了通用方法,再自己实现对应的 cooke() 方法。对应的测试用例为:

func TestTemplate(t *testing.T) {// 做西红柿xihongshi := &XiHongShi{}doCook(xihongshi)// 做炒鸡蛋chaojidan := &ChaoJiDan{}doCook(chaojidan)
}

行为型模式

行为型模式关注 对象之间的通信,这一类的设计模式中,有代理模式和选项模式

代理模式

代理模式可以为另一个对象提供一个替身或占位符,用来控制对这个对象的访问

比如:

type Seller interface {sell(name string)
}
​
// 火车站
type Station struct {stock int // 库存
}
​
func (station *Station) sell(name string) {if station.stock > 0 {station.stock--fmt.Printf("代理点中:%s买了一张票,剩余:%d \n", name, station.stock)} else {fmt.Println("票已售空")}
}
​
// 火车代理点
type StationProxy struct {station *Station // 持有一个火车站对象
}
​
func (proxy *StationProxy) sell(name string) {// 增加一些其他逻辑,比如权限校验if proxy.station.stock > 0 {proxy.station.stock--fmt.Printf("代理点中:%s买了一张票,剩余:%d \n", name, proxy.station.stock)} else {fmt.Println("票已售空")}
}

在这段代码中,StationProxy代理了 Station,代理类中持有被代理类对象,且和被代理类实现了统一接口。代理类主要是为了增加一种控制机制,在 StationProxy 实现的 sell 方法中,可以增加一些其他的逻辑

选项模式

选项模式是 Go 项目开发中经常用到的模式,比如 grpc/grpc-go的 NewServer 函数,uber-go/zap 包的 New 函数,都用到了选项模式

使用选项模式可以创建一个带有默认值的 struct 变量,并选择性的修改其中一些参数的值

Go 语言中不支持给参数设置默认值,为了既能够创建带默认值的实例,又能创建自定义参数的实例,不使用选项模式,一般有两种写法。

第一种方法是,分别创建两个用来创建实例的函数,一个可以创建带默认值的实例,一个可以定制化参数创建实例

const (defaultTimeout = 10defaultCaching = false
)
​
type Connection struct {addr    stringcache   booltimeout time.Duration
}
​
// 创建一个连接对象 需要路径参数
func NewConnect(addr string) (*Connection, error) {return &Connection{addr:    addr,cache:   defaultCaching,timeout: defaultTimeout,}, nil
}
​
// 创建一个连接对象,需要路径参数和一些配置参数
func NewConnectWithOptions(addr string, cache bool, timeout time.Duration) (*Connection, error) {return &Connection{addr:    addr,cache:   cache,timeout: timeout,}, nil
}

这种写法创建一个 Connection 实例,却要实现两个不同的函数,很麻烦,如果 Connection 结构体又增加了新属性,那么也要再编写一个带有这个新属性的构造方法

另一种写法是创建一个带默认值的选项,并用该选项创建实例:

const (defaultTimeout = 10defaultCaching = false
)
​
type Connection struct {addr    stringcache   booltimeout time.Duration
}
​
type ConnectionOptions struct {Caching boolTimeout time.Duration
}
​
// 默认选项
func NewDefaultOptions() *ConnectionOptions {return &ConnectionOptions{Caching: defaultCaching,Timeout: defaultTimeout,}
}
​
// 传入选项结构体和地址
func NewConnect(addr string, opts *ConnectionOptions) (*Connection, error) {return &Connection{addr:    addr,cache:   opts.Caching,timeout: opts.Timeout,}, nil
}

使用这种方式,虽然只需要一个函数来创建实例,但是调用 NewConnect 函数创建实例时,每次都要先创建一个 ConnectionOptions 结构体,操作起来比较麻烦

上面两种都有各自的缺点,使用选项模式可以更优雅的解决:

const (defaultTimeout = 10defaultCaching = false
)
​
type Connection struct {addr    stringcache   booltimeout time.Duration
}
​
// 配置选项
type options struct {timeout time.Durationcaching bool
}
​
// 接口类型 要实现 apply 方法
type Option interface {apply(*options)
}
​
// 函数类型起别名 类型是参数为 *options 返回为空的函数
type optionFunc func(*options)
​
func (f optionFunc) apply(o *options) {f(o)
}
​
func WithTimeout(t time.Duration) Option {// 把参数为 *options 返回为空的函数类型转换为 optionFunc 类型return optionFunc(func(o *options) {o.timeout = t})
}
​
func WithCaching(cache bool) Option {return optionFunc(func(o *options) {o.caching = cache})
}
​
// 创建一个连接对象 ... 表示可以有多个 Option 接口类型的参数
func NewConnect(addr string, ops ...Option) (*Connection, error) {options := options {timeout: defaultTimeout,caching: defaultCaching}for _, o := range ops {o.apply(&options)}return &Connection{addr:    addr,cache:   options.caching,timeout: options.timeout,}, nil
}

在这段代码中,首先定义了 options 结构体,它带有 timeout、caching 两个属性。接下来通过 NewConnect 创建连接,NewConnect 函数首先创建了一个带有默认值的 options 结构体,然后通过传入的 Option 参数,去修改 options 结构体,最后完成创建

在调用时,传入 WithXXX 格式的函数即可完成配置,因为函数返回值是 optionFunc 类型,而 optionFunc 类型又实现了 Option 接口,这就实现了动态设置 options 结构体变量的属性

选项模式有很多有点,例如:支持传递多个参数,在参数发生变化时保持兼容性,支持任意顺序传递参数,支持默认值,方便扩展等等

但是,为了实现选项模式,要增加很多代码,在开发中,要根据实际场景选择是否使用选项模式

选项模式适用的场景有:

  • 结构体参数很多,创建结构体时期望创建一个携带默认值的结构体变量,并选择性修改其中一些参数值

  • 结构体参数经常变动,变动时又不想修改创建实例的函数,比如:结构体新增一个 retry 参数,但是又不想在 NewConnect 入参列表中添加 retry int 这样的参数声明

如果结构体参数比较少,要慎重考虑要不要采用选项模式

总结

设计模式,是业界沉淀下来的针对特定场景的最佳解决方案,Go 项目常见的有 6 种设计模式,每种设计模式解决某一类场景

汇总成一张表:

参考:

设计模式 | 菜鸟教程 (runoob.com)

Go 语言项目开发实战 -11.Go常用设计模式

关键字:上海闵行区房价_淄博网站设计策划方案公司_市场调研报告模板范文_电商代运营

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: