在Golang中,如何传递配置参数给结构体,使其成为可选的?

2026-04-28 15:372阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1748个文字,预计阅读时间需要7分钟。

在Golang中,如何传递配置参数给结构体,使其成为可选的?

在Golang中构建结构体的时刻,我们需要通过可选参数方式来创建结构体。为了设计一个灵活的API来初始化结构体,我们可以采取以下步骤:

在Golang中,如何传递配置参数给结构体,使其成为可选的?

1. 定义结构体,包括所有可能的字段。

2.创建一个初始化函数,该函数接受结构体指针和可选参数。

3.在初始化函数中,根据传入的可选参数来设置结构体的字段。

以下是一个简单的示例代码片段,展示了如何通过可选参数模式来初始化结构体:

go

package main

import (fmt)

// User 定义了一个用户结构体。type User struct {Name stringAge intEmail stringActive boolProfile *Profile}

// Profile 定义了一个用户资料结构体。type Profile struct {Bio stringAvatar string}

// NewUser 创建一个新的User结构体实例。// 可选参数允许用户只提供部分信息。func NewUser(name, email string, age *int, active *bool, profile *Profile) *User {user :=&User{Name: name,Email: email,Active: false, // 默认值Profile: profile,}

if age !=nil {user.Age=*age}if active !=nil {user.Active=*active}

return user}

func main() {// 创建一个没有年龄和活跃状态的普通用户user1 :=NewUser(Alice, alice@example.com, nil, nil, nil)fmt.Printf(User1: %+v\n, user1)

// 创建一个有年龄但没有活跃状态的用户user2 :=NewUser(Bob, bob@example.com, new(int), nil, nil)user2.Age=30fmt.Printf(User2: %+v\n, user2)

// 创建一个完整的用户,包括资料user3 :=NewUser(Charlie, charlie@example.com, new(int), new(bool), &Profile{Bio: I love coding!,Avatar: path/to/avatar.jpg,})user3.Age=25user3.Active=truefmt.Printf(User3: %+v\n, user3)}

在这个例子中,`NewUser` 函数允许调用者通过可选参数来创建一个用户实例。如果某个字段不需要设置,可以传递`nil`或者不传递该参数。这样,我们可以灵活地创建用户,同时避免了不必要的错误和冗余代码。

写在前面的话

 Golang中构建结构体的时候,需要通过可选参数方式创建,我们怎么样设计一个灵活的API来初始化结构体呢。

让我们通过如下的代码片段,一步一步说明基于可选参数模式的灵活 API 怎么设计。

灵活 API 创建结构体说明 v1版本

如下 Client 是一个 客户端的sdk结构体,有 host和 port 两个参数,我们一般的用法如下:

package client type Client struct { host string port int } // NewClient 通过传递参数 func NewClient(host string, port int) *Client { return &Client{ host: host, port: port, } } func (c *Client) Call() error { // todo ...
return nil }

我们可以看到通过host和 port 两个参数可以创建一个 client 的 sdk。

调用的代码一般如下所示:

package main import ( "client" "log" ) func main() { cli := client.NewClient("localhost", 1122) if err := cli.Call(); err != nil { log.Fatal(err) } }

  

突然有一天,sdk 做了升级,增加了新的几个参数,如timeout超时时间,maxConn最大连接数, retry重试次数...

v2版本

sdk中的Client定义和创建结构体的 API变成如下:

package client import "time" type Client struct { host string port int timeout time.Duration maxConn int retry int } // NewClient 通过传递参数 func NewClient(host string, port int) *Client { return &Client{ host: host, port: port, timeout: time.Second, maxConn: 1, retry: 0, } } // NewClient 通过3个参数创建 func NewClientWithTimeout(host string, port int, timeout time.Duration) *Client { return &Client{ host: host, port: port, timeout: timeout, maxConn: 1, retry: 0, } } // NewClient 通过4个参数创建 func NewClientWithTimeoutAndMaxConn(host string, port int, timeout time.Duration, maxConn int) *Client { return &Client{ host: host, port: port, timeout: timeout, maxConn: maxConn, retry: 0, } } // NewClient 通过5个参数创建 func NewClientWithTimeoutAndMaxConnAndRetry(host string, port int, timeout time.Duration, maxConn int, retry int) *Client { return &Client{ host: host, port: port, timeout: timeout, maxConn: maxConn, retry: retry, } } func (c *Client) Call() error { // todo ...
return nil }

通过如上的创建 API 我们发现创建 Client 一下子多了NewClientWithTimeout/NewClientWithTimeoutAndMaxConn/NewClientWithTimeoutAndMaxConnAndRetry...

我们可以看到通过host和 port 等其他参数可以创建一个 client 的 sdk。

调用的代码一般如下所示:

package main import ( "client" "log" "time" ) func main() { cli := client.NewClientWithTimeoutAndMaxConnAndRetry("localhost", 1122, time.Second, 1, 0) if err := cli.Call(); err != nil { log.Fatal(err) } }

这个时候,我们发现 v2版本的 API 定义很不友好,参数组合的数量也特别多.

v3版本

我们需要把参数重构一下,是否可以把配置参数合并到一个结构体呢?

好,我们就把参数统一放到 Config 中,Client 中定义一个 cfg 成员

package client import "time" type Client struct { cfg Config } type Config struct { Host string Port int Timeout time.Duration MaxConn int Retry int } func NewClient(cfg Config) *Client { return &Client{ cfg: cfg, } } func (c *Client) Call() error { // todo ... return nil }

我们可以看到通过定义好的 Config参数可以创建一个 client 的 sdk。

调用的代码一般如下所示:

package main import ( "client" "log" "time" ) func main() { cli := client.NewClient(client.Config{ Host: "localhost", Port: 1122, Timeout: time.Second, MaxConn: 1, Retry: 0}) if err := cli.Call(); err != nil { log.Fatal(err) } }

  

这里我们发现新的问题出现了,Config 配置的成员都需要以大写开头,对外公开才可以使用,但做为一个 sdk,我们一般不建议对外导出这些成员。

我们该怎么办?

v4版本

我们回归到最初的定义,Client还是那个 Client,有很多配置成员变量,我们通过可选参数模式对 sdk 进行重构。

重构后的代码如下

package client import "time" type Client struct { host string port int timeout time.Duration maxConn int retry int } // 通过可选参数创建 func NewClient(opts ...func(client *Client)) *Client { // 创建一个空的Client cli := &Client{} // 逐个调用入参的可选参数函数,把每一个函数配置的参数复制到cli中 for _, opt := range opts { opt(cli) } return cli } // 把 host参数,传给函数参数 c *Client func WithHost(host string) func(*Client) { return func(c *Client) { c.host = host } } func WithPort(port int) func(*Client) { return func(c *Client) { c.port = port } } func WithTimeout(timeout time.Duration) func(*Client) { return func(c *Client) { c.timeout = timeout } } func WithMaxConn(maxConn int) func(*Client) { return func(c *Client) { c.maxConn = maxConn } } func WithRetry(retry int) func(*Client) { return func(c *Client) { c.retry = retry } } func (c *Client) Call() error { // todo ... return nil }

  

我们可以通过自由选择参数,创建一个 client 的 sdk。

调用的代码一般如下所示:

package main import ( "client" "log" "time" ) func main() { cli := client.NewClient( client.WithHost("localhost"), client.WithPort(1122), client.WithMaxConn(1), client.WithTimeout(time.Second)) if err := cli.Call(); err != nil { log.Fatal(err) } }

通过调用的代码可以看到,我们的 sdk 定义变的灵活和优美了。

开源最佳实践

最后我们看看按照这种方式的最佳实践项目。

gRpc

grpc.Dial(endpoint, opts...) // Dial creates a client connection to the given target. func Dial(target string, opts ...DialOption) (*ClientConn, error) { return DialContext(context.Background(), target, opts...) } func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) { cc := &ClientConn{ target: target, csMgr: &connectivityStateManager{}, conns: make(map[*addrConn]struct{}), dopts: defaultDialOptions(), blockingpicker: newPickerWrapper(), czData: new(channelzData), firstResolveEvent: grpcsync.NewEvent(), } for _, opt := range opts { opt.apply(&cc.dopts) } // ... }

完。

祝玩的开心~

参考:

functional-options的作者Dave Cheney

dave.cheney.net/2014/10/17/functional-options-for-friendly-apis

标签:结构体时

本文共计1748个文字,预计阅读时间需要7分钟。

在Golang中,如何传递配置参数给结构体,使其成为可选的?

在Golang中构建结构体的时刻,我们需要通过可选参数方式来创建结构体。为了设计一个灵活的API来初始化结构体,我们可以采取以下步骤:

在Golang中,如何传递配置参数给结构体,使其成为可选的?

1. 定义结构体,包括所有可能的字段。

2.创建一个初始化函数,该函数接受结构体指针和可选参数。

3.在初始化函数中,根据传入的可选参数来设置结构体的字段。

以下是一个简单的示例代码片段,展示了如何通过可选参数模式来初始化结构体:

go

package main

import (fmt)

// User 定义了一个用户结构体。type User struct {Name stringAge intEmail stringActive boolProfile *Profile}

// Profile 定义了一个用户资料结构体。type Profile struct {Bio stringAvatar string}

// NewUser 创建一个新的User结构体实例。// 可选参数允许用户只提供部分信息。func NewUser(name, email string, age *int, active *bool, profile *Profile) *User {user :=&User{Name: name,Email: email,Active: false, // 默认值Profile: profile,}

if age !=nil {user.Age=*age}if active !=nil {user.Active=*active}

return user}

func main() {// 创建一个没有年龄和活跃状态的普通用户user1 :=NewUser(Alice, alice@example.com, nil, nil, nil)fmt.Printf(User1: %+v\n, user1)

// 创建一个有年龄但没有活跃状态的用户user2 :=NewUser(Bob, bob@example.com, new(int), nil, nil)user2.Age=30fmt.Printf(User2: %+v\n, user2)

// 创建一个完整的用户,包括资料user3 :=NewUser(Charlie, charlie@example.com, new(int), new(bool), &Profile{Bio: I love coding!,Avatar: path/to/avatar.jpg,})user3.Age=25user3.Active=truefmt.Printf(User3: %+v\n, user3)}

在这个例子中,`NewUser` 函数允许调用者通过可选参数来创建一个用户实例。如果某个字段不需要设置,可以传递`nil`或者不传递该参数。这样,我们可以灵活地创建用户,同时避免了不必要的错误和冗余代码。

写在前面的话

 Golang中构建结构体的时候,需要通过可选参数方式创建,我们怎么样设计一个灵活的API来初始化结构体呢。

让我们通过如下的代码片段,一步一步说明基于可选参数模式的灵活 API 怎么设计。

灵活 API 创建结构体说明 v1版本

如下 Client 是一个 客户端的sdk结构体,有 host和 port 两个参数,我们一般的用法如下:

package client type Client struct { host string port int } // NewClient 通过传递参数 func NewClient(host string, port int) *Client { return &Client{ host: host, port: port, } } func (c *Client) Call() error { // todo ...
return nil }

我们可以看到通过host和 port 两个参数可以创建一个 client 的 sdk。

调用的代码一般如下所示:

package main import ( "client" "log" ) func main() { cli := client.NewClient("localhost", 1122) if err := cli.Call(); err != nil { log.Fatal(err) } }

  

突然有一天,sdk 做了升级,增加了新的几个参数,如timeout超时时间,maxConn最大连接数, retry重试次数...

v2版本

sdk中的Client定义和创建结构体的 API变成如下:

package client import "time" type Client struct { host string port int timeout time.Duration maxConn int retry int } // NewClient 通过传递参数 func NewClient(host string, port int) *Client { return &Client{ host: host, port: port, timeout: time.Second, maxConn: 1, retry: 0, } } // NewClient 通过3个参数创建 func NewClientWithTimeout(host string, port int, timeout time.Duration) *Client { return &Client{ host: host, port: port, timeout: timeout, maxConn: 1, retry: 0, } } // NewClient 通过4个参数创建 func NewClientWithTimeoutAndMaxConn(host string, port int, timeout time.Duration, maxConn int) *Client { return &Client{ host: host, port: port, timeout: timeout, maxConn: maxConn, retry: 0, } } // NewClient 通过5个参数创建 func NewClientWithTimeoutAndMaxConnAndRetry(host string, port int, timeout time.Duration, maxConn int, retry int) *Client { return &Client{ host: host, port: port, timeout: timeout, maxConn: maxConn, retry: retry, } } func (c *Client) Call() error { // todo ...
return nil }

通过如上的创建 API 我们发现创建 Client 一下子多了NewClientWithTimeout/NewClientWithTimeoutAndMaxConn/NewClientWithTimeoutAndMaxConnAndRetry...

我们可以看到通过host和 port 等其他参数可以创建一个 client 的 sdk。

调用的代码一般如下所示:

package main import ( "client" "log" "time" ) func main() { cli := client.NewClientWithTimeoutAndMaxConnAndRetry("localhost", 1122, time.Second, 1, 0) if err := cli.Call(); err != nil { log.Fatal(err) } }

这个时候,我们发现 v2版本的 API 定义很不友好,参数组合的数量也特别多.

v3版本

我们需要把参数重构一下,是否可以把配置参数合并到一个结构体呢?

好,我们就把参数统一放到 Config 中,Client 中定义一个 cfg 成员

package client import "time" type Client struct { cfg Config } type Config struct { Host string Port int Timeout time.Duration MaxConn int Retry int } func NewClient(cfg Config) *Client { return &Client{ cfg: cfg, } } func (c *Client) Call() error { // todo ... return nil }

我们可以看到通过定义好的 Config参数可以创建一个 client 的 sdk。

调用的代码一般如下所示:

package main import ( "client" "log" "time" ) func main() { cli := client.NewClient(client.Config{ Host: "localhost", Port: 1122, Timeout: time.Second, MaxConn: 1, Retry: 0}) if err := cli.Call(); err != nil { log.Fatal(err) } }

  

这里我们发现新的问题出现了,Config 配置的成员都需要以大写开头,对外公开才可以使用,但做为一个 sdk,我们一般不建议对外导出这些成员。

我们该怎么办?

v4版本

我们回归到最初的定义,Client还是那个 Client,有很多配置成员变量,我们通过可选参数模式对 sdk 进行重构。

重构后的代码如下

package client import "time" type Client struct { host string port int timeout time.Duration maxConn int retry int } // 通过可选参数创建 func NewClient(opts ...func(client *Client)) *Client { // 创建一个空的Client cli := &Client{} // 逐个调用入参的可选参数函数,把每一个函数配置的参数复制到cli中 for _, opt := range opts { opt(cli) } return cli } // 把 host参数,传给函数参数 c *Client func WithHost(host string) func(*Client) { return func(c *Client) { c.host = host } } func WithPort(port int) func(*Client) { return func(c *Client) { c.port = port } } func WithTimeout(timeout time.Duration) func(*Client) { return func(c *Client) { c.timeout = timeout } } func WithMaxConn(maxConn int) func(*Client) { return func(c *Client) { c.maxConn = maxConn } } func WithRetry(retry int) func(*Client) { return func(c *Client) { c.retry = retry } } func (c *Client) Call() error { // todo ... return nil }

  

我们可以通过自由选择参数,创建一个 client 的 sdk。

调用的代码一般如下所示:

package main import ( "client" "log" "time" ) func main() { cli := client.NewClient( client.WithHost("localhost"), client.WithPort(1122), client.WithMaxConn(1), client.WithTimeout(time.Second)) if err := cli.Call(); err != nil { log.Fatal(err) } }

通过调用的代码可以看到,我们的 sdk 定义变的灵活和优美了。

开源最佳实践

最后我们看看按照这种方式的最佳实践项目。

gRpc

grpc.Dial(endpoint, opts...) // Dial creates a client connection to the given target. func Dial(target string, opts ...DialOption) (*ClientConn, error) { return DialContext(context.Background(), target, opts...) } func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) { cc := &ClientConn{ target: target, csMgr: &connectivityStateManager{}, conns: make(map[*addrConn]struct{}), dopts: defaultDialOptions(), blockingpicker: newPickerWrapper(), czData: new(channelzData), firstResolveEvent: grpcsync.NewEvent(), } for _, opt := range opts { opt.apply(&cc.dopts) } // ... }

完。

祝玩的开心~

参考:

functional-options的作者Dave Cheney

dave.cheney.net/2014/10/17/functional-options-for-friendly-apis

标签:结构体时