Go语言必知必会100问题-11 使用选项模式

使用选项模式

在设计API时,可能会遇到一个问题:如何处理可选配置?有效的解决可选配置问题可以提高API的灵活性。本文通过一个具体示例说明处理可选配置的一些方法。该示例的要求是设计一个对外提供创建HTTP服务器的库函数。函数定义如下:

func NewServer(addr string, port int) (*http.Server, error) {// ...
}

假设上面的库函数已有人在愉快的使用。但是,在某些时候,有用户开始抱怨这个函数只提供addr和port初始化,缺少其他参数初始化(像写入超时设置和连接上下文等)。如果提供其他参数的初始化,需要修改NewServer函数,破坏了兼容性,迫使调用方也必须修改代码。与此同时,我们希望程序能够更加灵活,实现如下的逻辑。

  • 如果未设置端口,则使用默认端口

  • 如果端口为负,则返回错误

  • 如果端口为0,则使用随机端口

  • 否则,使用客户端提供的端口

在这里插入图片描述

怎么优雅的实现上述要求呢?下面来看看一些处理方法。

配置结构体

由于Go语言不支持函数可选参数,所以一种可能的方法是使用配置结构体来表达哪些是强制性参数,哪些是可选参数。例如,强制参数可以作为函数参数存在,而可选参数可以在Config结构体中处理。

type Config struct {Port        int
}
func NewServer(addr string, cfg Config) {
}

通过结构体方式修复了新增参数兼容性的问题,如果以后要添加新的参数,在Config结构体中定义即可。但是,这种方法没有解决上面端口设置策略逻辑。同时,我们应该注意如果没有初始化Config结构体字段,它会被初始为对应的零值。

  • 整数的零值为0

  • 浮点数的零值为0.0

  • 字符串的零值为“”

  • 切片、map、通道、指针、接口和函数的零值为nil

因此,在下面的示例中,结构体c1和c2是等价的。

c1 := httplib.Config{Port: 0,
}
c2 := httplib.Config{}

为了实现端口设置逻辑策略,我们需要找到一种方法来区分是用户特意设置端口为0还是没有设置端口(默认为0)。一种可能的解决方法是将Config结构体中的参数设置为对应类型的指针。使用*int,可以区分出值为0和没有设置值(零指针为nil)之间的差异。

type Config struct {Port        *int
}

虽然将Config结构体中的参数设置为指针有效,但是也有几个缺点:

第一个是客户端需要提供整数指针不方便,需要先创建一个整数变量,然后取整数变量的地址赋值给Config,像下面这样。只赋值一个字段问题不大,但是整个Config有很多字段,使用起来就不方便了。此外,添加的选项越多,代码就越复杂。

port := 0
config := httplib.Config{Port: &port,
}

第二个是在使用这个库的时候,如果采用默认的配置,客户端需要传递一个空结构对象,代码如下。这行代码看起来不直观友好,使用者不一定了解传空有特定的含义在里面。

httplib.NewServer("localhost", httplib.Config{})
创建者模式

在GoF设计模式书中,有一种模式叫创建者模式,该模式描述的是各种对象如何创建的问题。其核心理念是将对象的创建和对象本身分开,对于上述的Config结构体,需要有一个额外的结构ConfigBuilder,负责接收配置并创建Config对象。下面来看一个具体实现的例子,看看它是如何优雅实现我们所有需求的。

type Config struct {Port int
}
type ConfigBuilder struct {port *int
}
func (b *ConfigBuilder) Port(port int) *ConfigBuilder {b.port = &portreturn b
}
func (b *ConfigBuilder) Build() (Config, error) {cfg := Config{}if b.port == nil {cfg.Port = defaultHTTPPort} else {if *b.port == 0 {cfg.Port = randomPort()} else if *b.port < 0 {return Config{}, errors.New("port should be positive")} else {cfg.Port = *b.port}}return cfg, nil
}
func NewServer(addr string, config Config) (*http.Server, error) {// ...
}

ConfigBuilder结构体包含客户端配置项,并对外暴露一个Port方法用来设置端口值。通常,ConfigBuilder的配置方法会返回它本身,像上面的Port方法第一个返回值是*ConfigBuilder类型,以便可以使用方法链式调用连续设置配置项(像builder.Foo(“foo”).Bar(“bar”))。此外,ConfigBuilder还对外提供了一个Build方法,该方法会处理端口设置策略相关逻辑,并返回一个Config对象。

NOTE:建造者模式并不是只有一种实现方法。例如,有些人可能这样一种方法,即将定义端口值的逻辑放在Port方法里面而不是Build内部。本文的重点是介绍可以通过建造者模式创建对象,而不是枚举分析每种可能的建造者实现方法。

然后,客户端可以通过下面的代码来实现server的初始化(假设上面的实现放在httplib包中)。首先,客户端创建一个ConfigBuilder对象,用它来设置一个可选字段(像本文的端口)。然后,调用它的Build方法并检查错误信息,如果正确无误,则将配置传给NewServer创建一个server对象。

builder := httplib.ConfigBuilder{}
builder.Port(8080)
cfg, err := builder.Build()
if err != nil {return err
}
server, err := httplib.NewServer("localhost", cfg)
if err != nil {return err
}

采用上述实现方法使得端口管理更方便,不需传递整数指针,因为Port方法接收整数参数。但是,如果客户想要使用默认配置,仍然需要传一个空的配置结构体。

builder := httplib.ConfigBuilder{}
cfg, err := builder.Build()
if err != nil {return err
}
server, err := httplib.NewServer("localhost", cfg)

为什么将端口的异常值校验放在Build方法中而不是Port中,是因为我们想保持链式调用能力,函数就不能返回错误。如果客户端可以传递多个选项,但想精确处理端口无效的情况,会使错误处理更加复杂。这种情况下,更好的处理方法是采用下面的选项模式。

选项模式

选项模式是解决本文问题的第三种方法,尽管实现起来有细微的差别,但主要思想如下:

  • 有一个未导出的结构体,它包含各配置项:options结构体

  • 每个配置项都是返回一个相同类型的函数:type Option func(options *options) error. 例如,WithPort接收一个表示端口的int参数,并返回一个表示如何更新 options 结构体的Option函数。

在这里插入图片描述

下面是采用选项模式解决本文的问题,代码如下. WithPort返回的是一个闭包函数,并且是匿名的, 它引用函数体外的变量port. 该闭包函数是Option类型,并且实现了端口验证逻辑。options中的每个字段都需要创建一个类似于WithPort对外可导出函数,验证输入参数并更新options结构体中对应的字段值。

type options struct {port *int
}type Option func(options *options) errorfunc WithPort(port int) Option {return func(options *options) error {if port < 0 {return errors.New("port should be positive")}options.port = &portreturn nil}
}

采用选项模式时,NewServer实现代码如下。将选项字段作为可变参数传递,因此需要遍历所有选项字段来设置配置结构体值。

func NewServer(addr string, opts ...Option) (*http.Server, error) {var options optionsfor _, opt := range opts {err := opt(&options)if err != nil {return nil, err}}// At this stage, the options struct is built and contains the config// Therefore, we can implement our logic related to port configurationvar port intif options.port == nil {port = defaultHTTPPort} else {if *options.port == 0 {port = randomPort()} else {port = *options.port}}// ...
}

NewServer 内部首先创建一个空的 options结构体,然后,遍历每个可变参数opts, 执行它们更改option结构中的字段值,最后实现端口策略逻辑。 因为 NewServer 第二个参数是可变参数,所以调用方可以传递任意个参数,例如,下面传递端口和超时时间两个参数。

server, err := httplib.NewServer("localhost",httplib.WithPort(8080),httplib.WithTimeout(time.Second))

假如客户端需要默认配置,调用时就不用提供参数,调用代码如下。

server, err := httplib.NewServer("localhost")

本文讲述三种处理配置值的方法,虽然建造者模式相比配置结构体更好,但它有一些小缺点,使得选项模式成为Go语言中的惯用方法,它提供了一种方便且优雅设置对象字段值的方法,像Go中的gRPC库就采用了这种选项模式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/709928.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

服了,阿里云服务器和腾讯云服务器价格差不多怎么选择?

2024年阿里云服务器和腾讯云服务器价格战已经打响&#xff0c;阿里云服务器优惠61元一年起&#xff0c;腾讯云服务器62元一年&#xff0c;2核2G3M、2核4G、4核8G、8核16G、16核32G、16核64G等配置价格对比&#xff0c;阿腾云atengyun.com整理阿里云和腾讯云服务器详细配置价格表…

高级语言期末2011级B卷(计算机学院)

1.编写函数&#xff0c;实现按照如下公式计算的功能&#xff0c;其中n为自然数 #include <stdio.h>int fac(int n) {if(n0)return 1;elsereturn n*fac(n-1); }float fun(int n) {float flag;float sum0;for(int i0; i<n; i) {flagi/((i1)*fac(i2));sumflag;}return su…

重推请求之curl和fiddler

在实际的项目中会有出现问题&#xff0c;想重现的场景&#xff0c;比较重新调用一个服务&#xff0c;那么如何进行快速的重推请求呢&#xff0c;记录下来&#xff0c;方便备查。 主要有curl和fiddler两种方式&#xff0c;下面详细说。 方式一、curl 命令 curl 是一个利用URL规…

云上攻防-云服务篇弹性计算服务器云数据库实例元数据控制角色AK控制台接管

知识点: 1、云服务-弹性计算服务器-元数据&SSRF&AK 2、云服务-云数据库-外部连接&权限提升 章节点&#xff1a; 云场景攻防&#xff1a;公有云&#xff0c;私有云&#xff0c;混合云&#xff0c;虚拟化集群&#xff0c;云桌面等 云厂商攻防&#xff1a;阿里云&am…

租赁小程序|租赁系统|租赁软件开发带来高效运营

随着社会的不断发展和科技的不断进步&#xff0c;越来越多的企业开始关注设备租赁业务。设备租赁作为一种短期使用设备的方式&#xff0c;为企业提供了灵活和成本节约的优势。针对设备租赁业务的管理和提升企业竞争力的需求&#xff0c;很多企业选择定制开发设备租赁系统。本文…

js 面试 1判断变量是否是数组 2 检测数据类型方法

1 是否是数组 1) typeof 检测数据类型运算符 优点&#xff1a;使用简单 缺点&#xff1a;只能检测基本类型&#xff08;除null外&#xff09; console.log(typeof(10)) //Number console.log(typeof(false)) //boolean console.log(typeof(hello)) //string console.log(typeof…

vue使用gitshot生成gif

vue使用gitshot生成gif 问题背景 本文将介绍vue中使用gitshot生成gif。 问题分析 解决思路&#xff1a; 使用input组件上传一个视频&#xff0c;获取视频文件后用一个video组件进行播放&#xff0c;播放过程进行截图生成图片数组。 demo演示上传一个视频&#xff0c;然后生…

如何使用Docker部署IT-Tools并结合内网穿透实现公网访问本地工具箱服务

作为程序员&#xff0c;在日常工作中&#xff0c;需要借助一些工具来提高我们工作效率&#xff0c;IT-Tools是为开发人员度身打造的一套便捷在线工具。它提供全面功能&#xff0c;使开发者能以更高效方式完成任务。经由IT-Tools&#xff0c;开发人员能轻松应对各类技术挑战&…

C++之数组

1&#xff0c;概述 所谓数组&#xff0c;就是一个集合&#xff0c;里面存放了相同类型的数据元素 特点1&#xff1a;数组中没干过数据元素都是相同的数据类型 特点2&#xff1a;数组都是连续存放位置组成的 2&#xff0c;一维数组 2.1 一维数组的定义 一维数组定义有三种…

Leetcode583. 两个字符串的删除操作 -代码随想录

题目&#xff1a; 代码(首刷自解 2024年2月29日&#xff09;&#xff1a; class Solution { public:// 动态规划 好像和找最长公共子序列一样&#xff1f;int minDistance(string word1, string word2) {int sz1 word1.size();int sz2 word2.size();// dp initvector<vec…

SD-WAN技术:优化国内外服务器访问的关键

在全球化的商业环境中&#xff0c;企业经常需要在国内访问国外的服务器。然而&#xff0c;由于地理位置和网络架构的限制&#xff0c;这种跨国访问往往会遇到速度慢、延迟高等问题。SD-WAN&#xff08;软件定义广域网&#xff09;技术的兴起&#xff0c;为企业提供了一种新的解…

八股文打卡day24——数据库(1)

面试题&#xff1a;左连接和右连接的区别&#xff1f; 我的回答&#xff1a; 左连接的SQL语句是&#xff1a;左表 left join 右表 on 连接条件&#xff0c;表示以左表为基础&#xff0c;将左表的的所有记录与右表进行连接。即使右表中没有与左表匹配的记录&#xff0c;左连接…

Linux-Uboot命令

help命令 进入 uboot 的命令行模式后输入“help”或者“&#xff1f;”&#xff0c;然后按下回车即可查看当前 uboot 所支持的命令。 查看某一个命令的帮助信息&#xff1a;&#xff1f;命令名称 或 help命令名称 信息查询命令 常用的和信息查询有关的命令有 3 个…

Cookie、Session和JWT

摘要&#xff1a;Cookie、Session和JWT都不是什么新的技术了&#xff0c;最近用到了就比较和总结下。 我们知道http协议是无状态的&#xff0c;用户登录后如何验证和保存用户状态呢&#xff1f;下面来介绍 1. 使用Cookie和Session验证登录状态 session是保存在服务端的一种数…

STM32串口通信(发送与接收数据)

文章目录 前言一、介绍部分通信接口术语解释 串口通信简介硬件电路电平标准串口参数串口时序USART简介USART框图USRAT基本结构数据帧起始位检测波特率发生器CH340G 二、实例部分使用串口发送数据接线图代码实现重定向printf需要勾上Use MicroLIB中文不乱码方法 串口的发送与接收…

C++ 之LeetCode刷题记录(三十六)

&#x1f604;&#x1f60a;&#x1f606;&#x1f603;&#x1f604;&#x1f60a;&#x1f606;&#x1f603; 开始cpp刷题之旅。 目标&#xff1a;执行用时击败90%以上使用 C 的用户。 16. 最接近的三数之和 给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你…

Python 从文件中读取JSON 数据并解析转存

文章目录 文章开篇Json简介Json数据类型Json硬性规则Json数据转化网站Json和Dict类型转换json模块的使用Python数据和Json数据的类型映射json.dumps1.字典数据中含有**存在中文**2.json数据通过缩进符**美观输出**3.对Python数据类型中键进行**排序输出**4.json数据**分隔符的控…

【软件测试】接口调不通排查分析+常遇面试题总结

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、接口调不通&am…

【论文阅读】深度学习在过冷沸腾气泡动力学分割中的应用

Application of deep learning for segmentation of bubble dynamics in subcooled boiling 深度学习在过冷沸腾气泡动力学分割中的应用 期刊信息&#xff1a;International Journal of Multiphase Flow 2023 级别&#xff1a;EI检索 SCI升级版工程技术2区 SCI基础版工程技术3区…

(libusb) usb口自动刷新

文章目录 libusb自动刷新程序Code目录结构Code项目文件usb包code包 效果描述重置reset热拔插使用 END libusb 在操作USB相关内容时&#xff0c;有一个比较著名的库就是libusb。 官方网址&#xff1a;libusb 下载&#xff1a; 下载源码官方编好的库github&#xff1a;Release…