ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【Golang】创建有配置参数的结构体时,可选参数应该怎么传?

2022-06-10 12:31:15  阅读:162  来源: 互联网

标签:int 体时 client Golang host Client 参数 func port


写在前面的话

 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)
	}
        // ...
}

完。

祝玩的开心~

 

标签:int,体时,client,Golang,host,Client,参数,func,port
来源: https://www.cnblogs.com/voipman/p/16362779.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有