最近接触到越来越多有历史 “沉淀” 的 Go 项目,深感设计模式和及时干预的重要性。近期会分享一些设计模式,一起学习代码设计!
今天的分享的设计模式是:责任链和函数选项模式。在日常程序里是比较常用的。很多开源库中也有使用。
责任链模式
责任链模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许对象将请求沿处理程序链进行传递。
程序链既可以处理请求,也可以继续将请求传递给链中的下一个处理程序。请求沿着处理程序链传递,直到请求被处理或到达处理程序链的末端。
这类场景的例子包括:事件处理、日志记录和错误处理等类似拦截器注入的用法。
例子
我们将会创建一个简单例子来演示 Go 中的责任链模式。
在这个例子中,责任链将负责验证请求、打印错误日志和处理请求本身。
具体代码如下:
package service
import "fmt"
type Service interface {
HelloWorld(name string) (string, error)
}
type service struct {}
func (s service) HelloWorld(name string) (string, error) {
return fmt.Sprintf("Hello World from %s", name), nil
}
type validator struct {
next Service
}
func (v validator) HelloWorld(name string) (string, error) {
if len(name) <= 3 {
return "", fmt.Errorf("name length must be greater than 3")
}
return v.next.HelloWorld(name)
}
type logger struct {
next Service
}
func (l logger) HelloWorld(name string) (string, error) {
res, err := l.next(name)
if err != nil {
fmt.Println("error:", err)
return res, err
}
fmt.Println("HelloWorld method executed successfuly")
return res, err
}
func New() Service {
return logger{
next: validator {
next: service{},
},
}
}
入口 main 函数使用:
package main
import (
"fmt"
"service"
)
func main() {
s := service.New()
res, err := s.HelloWorld("煎鱼")
fmt.Println(res, err) // Hello World from 煎鱼
res, err := s.HelloWorld("edd")
fmt.Println(res, err) // name length must be greater than 3
}
在本例中,我们创建了一个实现 Service
接口的简单服务。Service
接口定义了一个方法 HelloWorld
,该方法将名称作为参数,并返回一条 Hello World 信息。
我们还创建了两个额外的结构体 validator
和 logger
,它们都实现了 Service
接口。
validator
结构会验证名称的长度,如果长度小于或等于 3,则返回错误信息。当 HelloWorld
方法成功执行时,logger
结构会打印错误日志和成功信息。
New
函数通过将 validator
和 logger
结构与 Service
结构链起来,创建了一个处理程序链。
Service
结构体的方法是链中的最后一个处理程序,如果请求通过验证,它将处理请求。
例子中 next 字段的作用是什么
next
字段用于保存链中的下一个处理程序。
当在一个处理程序上调用 HelloWorld
方法时,会使用 next
字段在链中的下一个处理程序上调用 HelloWorld
方法。
对照看可以得知,validator
和 logger
结构体中的 next
字段,是用于链式处理程序的关键字段。
函数选项模式
函数选项模式(Function Options Pattern)是一种很常见的设计模式,它允许开发人员通过使用 Options 作为参数,为函数或方法提供灵活且可定制的行为。
常用于 Go 库和框架,为用户提供简洁明了的 API。经常在我们使用的基础库能看到,例如:grpc-go。
例子
我们有一个用 Go 表示 HTTP 服务器的 Server 结构体作为基础。
我们希望为用户提供配置 HTTP Server 各方面的功能,例如:监听的端口、超时时间以及是否启用日志记录等。
具体代码如下:
package main
import "fmt"
type Server struct {
Host string
Port int
Protocol string
Timeout int
}
type ServerOption func(*Server)
func WithHost(host string) ServerOption {
return func(s *Server) {
s.Host = host
}
}
func WithPort(port int) ServerOption {
return func(s *Server) {
s.Port = port
}
}
func WithProtocol(protocol string) ServerOption {
return func(s *Server) {
s.Protocol = protocol
}
}
func WithTimeout(timeout int) ServerOption {
return func(s *Server) {
s.Timeout = timeout
}
}
func NewServer(options ...ServerOption) *Server {
server := &Server{
Host: "localhost",
Port: 8080,
Protocol: "http",
Timeout: 30,
}
for _, option := range options {
option(server)
}
return server
}
func main() {
server := NewServer(
WithHost("eddycjy.com"),
WithPort(9000),
WithProtocol("https"),
WithTimeout(60),
)
fmt.Printf("Server: %+v\n", server)
}
解决了什么问题
参数数量不断增加:随着参数数量的增加,传统的函数签名可能会变得臃肿不堪。函数选项模式允许添加新的选项,而无需更改函数签名或破坏现有代码。
可读性:当一个函数需要多个参数,尤其是同一类型的参数时,很难记住每个参数的顺序和含义。有了函数选项(Option),每个选项都有清晰的标签,不言自明,从而提高了代码的可读性。
缺省值:函数选项模式允许你轻松地为选项提供默认值。如果在调用函数时没有提供选项,就会使用默认值。
可选参数:在某些情况下,你可能想让某些参数成为可选参数。函数选项模式可以让你轻松做到这一点,并提供一个灵活的接口。
封装和独立:每个选项都是一个函数,可以包含自己的程序逻辑。这样就可以封装每个选项的逻辑,并保持主函数简洁明了。
总结
一个好的设计模式,可以让你的项目更具有易读性。程序编写和使用起来更加的舒心。今天给大家介绍的责任链模式和函数可选模式,是非常常用的。
推荐大家可以尝试应用到自己的 Go 项目中去,想必会有非常大的帮助!
推荐阅读
关注和加煎鱼微信,
一手消息和知识,拉你进技术交流群👇
你好,我是煎鱼,出版过 Go 畅销书《Go 语言编程之旅》,再到获得 GOP(Go 领域最有观点专家)荣誉,点击蓝字查看我的出书之路。
日常分享高质量文章,输出 Go 面试、工作经验、架构设计,加微信拉读者交流群,和大家交流!