Golang 函数式选项模式
Go (Golang) Functiona 选项模式是一种方式,一种在 Go 中构建结构的模式,它通过设计一组非常富有表现力和灵活的 API 来帮助配置和初始化结构。让我们看一下代码片段,看看我们可以使用哪些选项,以及功能选项模式如何以及何时对我们有用。
示例:在 Go 中构建服务器包
在此示例中,我们查看 Go 中的服务器包,但它可以是第三方客户端使用的任何东西,例如自定义 SDK 或记录器库。
package server
type Server {
host string
port int
}
func New(host string, port int) *Server {
return &Server{host, port}
}
func (s *Server) Start() error {
// todo
}
以下是客户端如何导入和使用您的服务器包
package main
import (
"log"
"github.com/acme/pkg/server"
)
func main() {
svr := server.New("localhost", 1234)
if err := svr.Start(); err != nil {
log.Fatal(err)
}
}
现在,在这种情况下,我们如何扩展服务器的配置选项?有几个选项
为每个不同的配置选项声明一个构造函数
定义一个新的 Config 结构来保存配置信息
使用功能选项模式
让我们一一探讨这 3 个示例,并分析每个示例的优缺点。
选项 1:为每个配置选项声明一个新的构造函数
如果您知道您的配置选项不会很幸运地改变并且您的配置选项很少,那么这可能是一个好方法。所以很容易为每个不同的配置选项创建新的方法。
package server
type Server {
host string
port int
timeout time.Duration
maxConn int
}
func New(host string, port int) *Server {
return &Server{host, port, time.Minute, 100}
}
func NewWithTimeout(host string, port int, timeout time.Duration) *Server {
return &Server{host, port, timeout}
}
func NewWithTimeoutAndMaxConn(host string, port int, timeout time.Duration, maxConn int) *Server {
return &Server{host, port, timeout, maxConn}
}
func (s *Server) Start() error {
// todo
}
以及下面的相关客户端实现
package main
import (
"log"
"github.com/acme/pkg/server"
)
func main() {
svr := server.NewWithTimeoutAndMaxConn("localhost", 1234, 30*time.Second, 10)
if err := svr.Start(); err != nil {
log.Fatal(err)
}
}
当配置选项的数量增长或经常变化时,这种方法不是很灵活。您还需要为每个新的配置选项或一组配置选项创建新的构造函数。
选项 2:使用自定义 Config 结构
这是最常见的方法,当需要配置很多选项时可以很好地工作。您可以创建一个名为“Config”的新导出类型,其中包含服务器的所有配置选项。这可以在不破坏服务器构造函数 API 的情况下轻松扩展。添加新选项或删除旧选项时,我们不必更改其定义
package server
type Server {
cfg Config
}
type Config struct {
host string
port int
timeout time.Duration
maxConn int
}
func New(cfg Config) *Server {
return &Server{cfg}
}
func (s *Server) Start() error {
// todo
}
以及下面使用新的 Config 结构的相对客户端实现
package main
import (
"log"
"github.com/acme/pkg/server"
)
func main() {
svr := server.New(server.Config{"localhost", 1234, 30*time.Second, 10})
if err := svr.Start(); err != nil {
log.Fatal(err)
}
}
这种方法很灵活,允许我们为我们的服务器(或 SDK 客户端或您正在构建的任何东西)定义一个固定类型 (server.Config) 和一组稳定的 API 来配置我们的服务器,例如server.New(cfg server.Config). 唯一的问题是,当添加新选项或删除旧选项时,我们仍然需要对 Config 结构的结构进行重大更改。但这仍然是迄今为止最好的和更有用的选择。
选项 3:功能选项模式
此选项配置问题的更好替代方案是功能选项设计模式。你可能以前在 Go 项目中看到或听说过函数式选项模式,但在这个例子中,我们将详细分解它的结构和特征。
package server
type Server {
host string
port int
timeout time.Duration
maxConn int
}
func New(options ...func(*Server)) *Server {
svr := &Server{}
for _, o := range options {
o(svr)
}
return svr
}
func (s *Server) Start() error {
// todo
}
func WithHost(host string) func(*Server) {
return func(s *Server) {
s.host = host
}
}
func WithPort(port int) func(*Server) {
return func(s *Server) {
s.port = port
}
}
func WithTimeout(timeout time.Duration) func(*Server) {
return func(s *Server) {
s.timeout = timeout
}
}
func WithMaxConn(maxConn int) func(*Server) {
return func(s *Server) {
s.maxConn = maxConn
}
}
以及下面使用新功能选项模式的相关客户端实现
package main
import (
"log"
"github.com/acme/pkg/server"
)
func main() {
svr := server.New(
server.WithHost("localhost"),
server.WithPort(8080),
server.WithTimeout(time.Minute),
server.WithMaxConn(120),
)
if err := svr.Start(); err != nil {
log.Fatal(err)
}
}
功能选项模式允许我们为服务器的每个和任何可能的配置定义一个固定的类型签名,使用func(*Server)类型签名购买我们可以创建任何要传递给服务器的选项。默认情况下,我们的选项也是可选的,因此可以轻松交换任何选项而不会出现任何重大问题。考虑到类型定义的富有表现力的设计和自动记录的性质,这种方法也很好,每个方法都为您的服务器定义选项和选项类型。