相熟 Python 开发的同学都晓得,Python 有默认参数的存在,使得咱们在实例化一个对象的时候,能够依据须要来选择性的笼罩某些默认参数,以此来决定如何实例化对象。当一个对象有多个默认参数时,这个个性十分好用,可能优雅地简化代码。
而 Go 语言从语法上是不反对默认参数的,所以为了实现既能通过默认参数创建对象,又能通过传递自定义参数创建对象,咱们就须要通过一些编程技巧来实现。对于这些程序开发中的常见问题,软件行业的先行者们总结了许多解决常见场景编码问题的最佳实际,这些最佳实际起初成为了咱们所说的设计模式。其中选项模式在 Go 语言开发中会常常用到。
通常咱们有以下三种办法来实现通过默认参数创建对象,以及通过传递自定义参数创建对象:
- 应用多个构造函数
- 默认参数选项
- 选项模式
通过多构造函数实现
第一种形式是通过多构造函数实现,上面是一个简略例子:
package main
import "fmt"
const (
defaultAddr = "127.0.0.1"
defaultPort = 8000
)
type Server struct {
Addr string
Port int
}
func NewServer() *Server {
return &Server{
Addr: defaultAddr,
Port: defaultPort,
}
}
func NewServerWithOptions(addr string, port int) *Server {
return &Server{
Addr: addr,
Port: port,
}
}
func main() {s1 := NewServer()
s2 := NewServerWithOptions("localhost", 8001)
fmt.Println(s1) // &{127.0.0.1 8000}
fmt.Println(s2) // &{localhost 8001}
}
这里咱们为 Server 构造体实现了两个构造函数:
- NewServer:无需传递参数即可间接返回 Server 对象
- NewServerWithOptions:须要传递 addr 和 port 两个参数来结构 Server 对象
如果通过默认参数创立的对象即可满足需要,不须要对 Server 进行定制时,咱们能够应用 NewServer 来生成对象(s1)。而如果须要对 Server 进行定制时,咱们则能够应用 NewServerWithOptions 来生成对象(s2)。
通过默认参数选项实现
另外一种实现默认参数的计划,是为要生成的构造体对象定义一个选项构造体,用来生成要创建对象的默认参数,代码实现如下:
package main
import "fmt"
const (
defaultAddr = "127.0.0.1"
defaultPort = 8000
)
type Server struct {
Addr string
Port int
}
type ServerOptions struct {
Addr string
Port int
}
func NewServerOptions() *ServerOptions {
return &ServerOptions{
Addr: defaultAddr,
Port: defaultPort,
}
}
func NewServerWithOptions(opts *ServerOptions) *Server {
return &Server{
Addr: opts.Addr,
Port: opts.Port,
}
}
func main() {s1 := NewServerWithOptions(NewServerOptions())
s2 := NewServerWithOptions(&ServerOptions{
Addr: "localhost",
Port: 8001,
})
fmt.Println(s1) // &{127.0.0.1 8000}
fmt.Println(s2) // &{localhost 8001}
}
咱们为 Server 构造体专门实现了一个 ServerOptions 用来生成默认参数,调用 NewServerOptions 函数即可取得默认参数配置,构造函数 NewServerWithOptions 接管一个 *ServerOptions 类型作为参数。所以咱们能够通过以下两种形式来实现性能:
- 间接将调用 NewServerOptions 函数的返回值传递给 NewServerWithOptions 来实现通过默认参数生成对象(s1)
- 通过手动结构 ServerOptions 配置来生成定制对象(s2)
通过选项模式实现
以上两种形式尽管都可能实现性能,但却有以下毛病:
- 通过多构造函数实现的计划须要咱们在实例化对象时别离调用不同的构造函数,代码封装性不强,会给调用者减少应用累赘。
- 通过默认参数选项实现的计划须要咱们事后结构一个选项构造,当应用默认参数生成对象时代码看起来比拟冗余。
而选项模式能够让咱们更为优雅地解决这个问题。代码实现如下:
package main
import "fmt"
const (
defaultAddr = "127.0.0.1"
defaultPort = 8000
)
type Server struct {
Addr string
Port int
}
type ServerOptions struct {
Addr string
Port int
}
type ServerOption interface {apply(*ServerOptions)
}
type FuncServerOption struct {f func(*ServerOptions)
}
func (fo FuncServerOption) apply(option *ServerOptions) {fo.f(option)
}
func WithAddr(addr string) ServerOption {
return FuncServerOption{f: func(options *ServerOptions) {options.Addr = addr},
}
}
func WithPort(port int) ServerOption {
return FuncServerOption{f: func(options *ServerOptions) {options.Port = port},
}
}
func NewServer(opts ...ServerOption) *Server {
options := ServerOptions{
Addr: defaultAddr,
Port: defaultPort,
}
for _, opt := range opts {opt.apply(&options)
}
return &Server{
Addr: options.Addr,
Port: options.Port,
}
}
func main() {s1 := NewServer()
s2 := NewServer(WithAddr("localhost"), WithPort(8001))
s3 := NewServer(WithPort(8001))
fmt.Println(s1) // &{127.0.0.1 8000}
fmt.Println(s2) // &{localhost 8001}
fmt.Println(s3) // &{127.0.0.1 8001}
}
乍一看咱们的代码简单了很多,但其实调用构造函数生成对象的代码复杂度是没有扭转的,只是定义上的简单。
咱们定义了 ServerOptions 构造体用来配置默认参数。因为 Addr 和 Port 都有默认参数,所以 ServerOptions 的定义和 Server 定义是一样的。但有肯定复杂性的构造体中可能会有些参数没有默认参数,必须让用户来配置,这时 ServerOptions 的字段就会少一些,大家能够按需定义。
同时,咱们还定义了一个 ServerOption 接口和实现了此接口的 FuncServerOption 构造体,它们的作用是让咱们可能通过 apply 办法为 ServerOptions 构造体独自配置某项参数。
咱们能够别离为每个默认参数都定义一个 WithXXX 函数用来配置参数,如这里定义的 WithAddr 和 WithPort,这样用户就能够通过调用 WithXXX 函数来定制须要笼罩的默认参数。
此时默认参数定义在构造函数 NewServer 中,构造函数的接管一个不定长参数,类型为 ServerOption,在构造函数外部通过一个 for 循环调用每个传进来的 ServerOption 对象的 apply 办法,将用户配置的参数顺次赋值给构造函数外部的默认参数对象 options 中,以此来替换默认参数,for 循环执行实现后,失去的 options 对象将是最终配置,将其属性顺次赋值给 Server 即可生成新的对象。
总结
通过 s2 和 s3 的打印后果能够发现,应用选项模式实现的构造函数更加灵便,相较于前两种实现,选项模式中咱们能够自在的更改其中任意一项或多项默认配置。
尽管选项模式的确会多写一些代码,但少数状况下这都是值得的。比方 Google 的 gRPC 框架 Go 语言实现中创立 gRPC server 的构造函数 NewServer 就应用了选项模式,感兴趣的同学能够看下其源码的实现思维其实和这里的示例程序一模一样。
以上就是我对于 Golang 选项模式的一点教训,心愿明天的分享可能给你带来一些帮忙。
举荐浏览
服务端渲染根底
云原生灰度更新实际