【go语言规范】 使用函数式选项 Functional Options 模式处理可选配置

如何处理可选配置?

  1. Config Struct 方式 (config-struct/main.go)
    这是最简单的方式,使用一个配置结构体:
  • 定义了一个简单的 Config 结构体,包含 Port 字段
  • 创建服务器时直接传入配置对象
  • 优点:简单直接
  • 缺点:不够灵活,所有字段都必须设置值,即使只想修改其中一个
  1. Builder 模式 (builder/main.go)
    使用建造者模式:
  • 定义 ConfigBuilder 结构体来构建配置
  • 提供链式调用方法如 Port()
  • 通过 Build() 方法验证并生成最终配置
  • 优点:支持链式调用,可以进行参数验证
  • 缺点:需要编写较多样板代码
  1. 函数选项模式 (functional-options/main.go)
    这是最灵活的方式:
  • 定义 Option 函数类型用于修改配置
  • 使用 WithXXX 函数创建配置选项
  • 支持默认值和参数验证
  • 可以方便地添加新的配置项
  • 使用示例:
NewServer("localhost", WithPort(8080))

详细代码转载go语言经典100错

package mainimport ("errors""net/http"
)// 默认HTTP服务端口
const defaultHTTPPort = 8080// options 结构体用于存储所有配置选项
type options struct {port *int // 使用指针以区分是否设置了端口
}// Option 定义了功能选项的函数类型
// 每个选项都是一个函数,接收 options 指针并返回错误
type Option func(options *options) error// WithPort 创建一个设置端口的选项
// 这是一个工厂函数,返回一个闭包
// 闭包可以访问外部函数 WithPort 中的 port 参数
func WithPort(port int) Option {// 这里返回的匿名函数就是一个闭包// 它可以访问并持有外部函数 WithPort 的 port 参数// 即使 WithPort 函数执行完毕,返回的闭包仍然可以访问 port 值return func(options *options) error {if port < 0 {return errors.New("port should be positive")}options.port = &portreturn nil}
}// NewServer 创建一个新的 HTTP 服务器
// addr: 服务器地址
// opts: 可变参数,包含所有功能选项
func NewServer(addr string, opts ...Option) (*http.Server, error) {// 创建选项实例var options options// 应用所有选项for _, opt := range opts {err := opt(&options)if err != nil {return nil, err}}// 确定最终使用的端口var port intif options.port == nil {// 未设置端口,使用默认值port = defaultHTTPPort} else {if *options.port == 0 {// 端口为0,使用随机端口port = randomPort()} else {// 使用指定的端口port = *options.port}}_ = portreturn nil, nil
}// client 展示如何使用功能选项模式
func client() {_, _ = NewServer("localhost", WithPort(8080))
}// randomPort 返回一个随机端口号
// 注:这是一个示例实现,实际应用中应该使用真正的随机端口生成方法
func randomPort() int {return 4 // 通过掷骰子选择的,保证随机(开玩笑的)
}

函数选项模式的优势:

  1. 支持默认值
  2. 参数可选
  3. 易于扩展
  4. 支持参数验证
  5. API 使用简洁

这是 Go 语言中最推荐的配置模式之一,特别适合那些有很多可选配置项的情况。


在k8s 源码中,可以找到如下例子使用了 functional-options方法

Kubernetes 源码中大量使用了 Functional Options 模式,我来展示一个典型的例子:

ClientSet 的创建

// NewForConfig creates a new Clientset for the given config.
func NewForConfig(c *rest.Config) (*Clientset, error) {configShallowCopy := *cif configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)}var cs Clientset// ... 其他代码return &cs, nil
}// NewForConfigOrDie creates a new Clientset for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *Clientset {cs, err := NewForConfig(c)if err != nil {panic(err)}return cs
}

RESTClient 的选项模式

// RESTClientFor returns a RESTClient that satisfies the requested attributes on a client Config object.
func RESTClientFor(config *Config) (*RESTClient, error) {// ...existing code...return NewRESTClient(config.Host,config.APIPath,config.ContentConfig,config.QPS,config.Burst,config.RateLimiter,config.WrapTransport,)
}

Informer 的创建选项

// SharedInformerOption defines the functional option type for SharedInformerFactory.
type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory// WithCustomResyncConfig sets a custom resync period for the specified informer types.
func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption {return func(factory *sharedInformerFactory) *sharedInformerFactory {for obj, resyncPeriod := range resyncConfig {factory.customResync[obj] = resyncPeriod}return factory}
}// WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory.
func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption {return func(factory *sharedInformerFactory) *sharedInformerFactory {factory.tweakListOptions = tweakListOptionsreturn factory}
}// NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options.
func NewSharedInformerFactoryWithOptions(client kubernetes.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory {factory := &sharedInformerFactory{client:           client,namespace:        v1.NamespaceAll,defaultResync:    defaultResync,informers:        make(map[reflect.Type]cache.SharedIndexInformer),startedInformers: make(map[reflect.Type]bool),customResync:     make(map[reflect.Type]time.Duration),}// 应用所有选项for _, opt := range options {factory = opt(factory)}return factory
}

这些例子展示了 Kubernetes 如何使用 Functional Options 模式来:

  1. 灵活配置

    • 客户端配置
    • 资源监控器配置
    • REST 客户端选项
  2. 默认值处理

    • 速率限制
    • 同步周期
    • API 路径
  3. 可扩展性

    • 自定义选项
    • 可选功能
    • 行为修改

这种模式在 Kubernetes 中广泛使用,因为它:

  • 提供了良好的 API 扩展性
  • 保持了向后兼容性
  • 使配置更加灵活
  • 代码更易于维护和测试

Function Options 模式与接口定义的区别

你提出了一个很好的问题。让我们来分析一下:

接口与 Function Options 的不同点

  1. 接口定义的原则
// 接口应该在使用处定义
type userRepository interface {Save(user User) errorFind(id string) (*User, error)
}type UserService struct {repo userRepository  // 在使用处定义接口
}
  1. Function Options 的特点
// 选项定义在包的实现端
type Option func(*options) errorfunc WithPort(port int) Option {return func(o *options) error {o.port = &portreturn nil}
}

为什么 Function Options 不需要在客户端定义

  1. 不是抽象依赖

    • Function Options 是配置模式,不是依赖抽象
    • 它是实现细节的一部分,不是接口契约
  2. 封装性

// 实现包中
type options struct {port    *int    // 私有字段timeout *time.Duration
}// 客户端使用
server, _ := NewServer("localhost", WithPort(8080),WithTimeout(5 * time.Second),
)
  1. API 稳定性
    • 选项函数提供了稳定的公共 API
    • 内部 options 结构体可以随时更改而不影响客户端

实际例子

// 服务端定义(正确的方式)
package servertype Option func(*options) errorfunc WithPort(port int) Option { ... }
func WithTimeout(t time.Duration) Option { ... }// 客户端使用(简洁清晰)
package clientfunc main() {server.NewServer("localhost",server.WithPort(8080),server.WithTimeout(5 * time.Second),)
}

总结

  • 接口定义在使用处是为了依赖抽象
  • Function Options 定义在实现处是为了配置灵活性
  • 两者解决的是不同的问题:
    • 接口:依赖反转
    • Options:配置管理

Go Functional Options 模式与 Java 构建器模式的对比

Java 构建器模式

public class Server {private final String addr;private final int port;private final int timeout;private Server(Builder builder) {this.addr = builder.addr;this.port = builder.port;this.timeout = builder.timeout;}public static class Builder {private String addr;private int port = 8080;  // 默认值private int timeout = 30;  // 默认值public Builder(String addr) {this.addr = addr;}public Builder port(int port) {this.port = port;return this;}public Builder timeout(int timeout) {this.timeout = timeout;return this;}public Server build() {return new Server(this);}}
}

Go Functional Options 模式

type options struct {port    *inttimeout *time.Duration
}type Option func(*options) errorfunc WithPort(port int) Option {return func(o *options) error {if port < 0 {return errors.New("invalid port")}o.port = &portreturn nil}
}func NewServer(addr string, opts ...Option) (*Server, error) {var options optionsfor _, opt := range opts {if err := opt(&options); err != nil {return nil, err}}// ...
}

主要区别

  1. 语法风格

    • Java: 链式调用,方法返回 this
    • Go: 函数式风格,使用闭包
  2. 错误处理

    • Java: 通常在 build() 时检查参数有效性
    • Go: 每个选项函数都可以返回错误
  3. 使用方式

// Java
Server server = new Server.Builder("localhost").port(8080).timeout(30).build();
// Go
server, err := NewServer("localhost", WithPort(8080),WithTimeout(30 * time.Second))
  1. 扩展性

    • Java: 需要修改 Builder 类
    • Go: 只需添加新的选项函数
  2. 默认值处理

    • Java: 在 Builder 构造函数中设置
    • Go: 在 NewServer 函数中处理

Go 方式的优势

  1. 更符合 Go 语言特性

    • 利用函数式编程特性
    • 使用错误返回值而不是异常
  2. 更灵活的扩展性

    • 选项函数可以在任何包中定义
    • 不需要修改核心代码
  3. 更好的参数验证

    • 每个选项都可以独立验证
    • 立即返回错误
  4. 更简洁的 API

    • 没有额外的 Builder 类
    • 参数传递更直观

使用场景建议

  • Java Builder: 当参数较多且有复杂的构建逻辑时
  • Go Options: 当需要灵活配置且重视可扩展性时

是的,Java 的 Builder 模式和 Go 的 Functional Options 模式都是配置模式的不同实现。让我对比一下:

Java Builder 模式

public class Server {private final int port;private final int timeout;public static class Builder {// 可配置项,有默认值private int port = 8080;private int timeout = 30;// 链式配置方法public Builder port(int port) {this.port = port;return this;}// 构建最终对象public Server build() {// 参数验证if (port < 0) {throw new IllegalArgumentException("port must be positive");}return new Server(this);}}
}

Go Functional Options 模式

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

共同点

  1. 配置的灵活性

    • 都支持可选参数
    • 都有默认值机制
    • 都支持参数验证
  2. 构建过程控制

    • Java: 通过 build() 方法
    • Go: 通过 NewServer() 函数
  3. 参数验证

    • 都可以在构建过程中进行参数验证

主要区别

  1. 语法风格
// Java: 链式调用
Server server = new Server.Builder().port(8080).timeout(30).build();
// Go: 函数式风格
server, err := NewServer("localhost",WithPort(8080),WithTimeout(30 * time.Second),
)
  1. 错误处理

    • Java: 使用异常
    • Go: 返回错误值
  2. 扩展方式

    • Java: 需要修改 Builder 类
    • Go: 只需添加新的选项函数

选择建议

  1. 使用 Java Builder 模式当:

    • 需要严格的参数校验
    • 对象构建过程复杂
    • 需要不可变对象
  2. 使用 Go Functional Options 模式当:

    • 需要高度灵活性
    • 配置项可能在不同包中扩展
    • 错误处理更为重要

两种模式都是优秀的配置模式实现,选择哪种主要取决于:

  • 使用的编程语言
  • 项目的具体需求
  • 团队的编程风格偏好

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

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

相关文章

leetcode 2585. 获得分数的方法数

题目如下 数据范围 莫要被困难的外衣骗了&#xff0c;本题就是有数量限制的完全背包问题。显然我们可以令 f(x,y)为当有x种题目时分数为y时的方法数 令某种题目的数量为k 那么方法数应该是 f(x,y) f(x - 1,y - k * (分值))其中(0 < k < 题目数量)通过代码 class So…

深入理解JavaScript中的异步编程与Promise

一、引言 在JavaScript的世界中&#xff0c;异步编程是一个核心概念&#xff0c;尤其是在处理网络请求、文件操作或任何可能阻塞主线程的任务时。本文将深入探讨JavaScript中的异步编程模型&#xff0c;特别是Promise对象的使用。 二、异步编程基础 2.1 什么是异步编程&…

VS Code 如何搭建C/C++开发环境

目录 1.VS Code是什么 2. VS Code的下载和安装 2.1 下载和安装 2.2.1 下载 2.2.2 安装 2.2 环境的介绍 2.3 安装中文插件 3. VS Code配置C/C开发环境 3.1 下载和配置MinGW-w64编译器套件 3.1.1 下载 3.1.2 配置 3.2 安装C/C插件 3.3 重启VSCode 4. 在VSCode上编写…

如何查询网站是否被百度蜘蛛收录?

一、使用site命令查询 这是最直接的方法。在百度搜索框中输入“site:你的网站域名”&#xff0c;例如“site:example.com”&#xff08;请将“example.com”替换为你实际的网站域名&#xff09;。如果搜索结果显示了你的网站页面&#xff0c;并且显示了收录的页面数量&#xf…

数仓搭建:DWS层(服务数据层)

DWS层示例: 搭建日主题宽表 需求 维度 步骤 在hive中建数据库dws >>建表 CREATE DATABASE if NOT EXISTS DWS; 建表sql CREATE TABLE yp_dws.dws_sale_daycount( --维度 city_id string COMMENT 城市id, city_name string COMMENT 城市name, trade_area_id string COMME…

伪类选择器

作用&#xff1a;选中特殊状态的元素 一、动态伪类 1. :link 超链接 未被访问 的状态。 2. :visited 超链接 访问过 的状态。 3. :hover 鼠标 悬停 在元素上的状态。 4. :active 元素 激活 的状态。 什么是激活&#xff1f; —— 按下鼠标不松开。 注意点&#xf…

Kubernetes:EKS 中 Istio Ingress Gateway 负载均衡器配置及常见问题解析

引言 在云原生时代&#xff0c;Kubernetes 已经成为容器编排的事实标准。AWS EKS (Elastic Kubernetes Service) 作为一项完全托管的 Kubernetes 服务&#xff0c;简化了在 AWS 上运行 Kubernetes 的复杂性。Istio 作为服务网格领域的佼佼者&#xff0c;为微服务提供了流量管理…

Docker安装Kafka(不依赖ZooKeeper)

创建docker-compose.yaml version: "3.9" #版本号 services:kafka:image: apache/kafka:3.9.0container_name: kafkahostname: kafkaports:- 9092:9092 # 容器内部之间使用的监听端口- 9094:9094 # 容器外部访问监听端口environment:KAFKA_NODE_ID: 1KAFKA_PROCES…

挪车小程序挪车二维码php+uniapp

一款基于FastAdminThinkPHP开发的匿名通知车主挪车微信小程序&#xff0c;采用匿名通话的方式&#xff0c;用户只能在有效期内拨打车主电话&#xff0c;过期失效&#xff0c;从而保护车主和用户隐私。提供微信小程序端和服务端源码&#xff0c;支持私有化部署。 更新日志 V1.0…

unity 设置可配置文件asset

使用可序列化类保存配置&#xff0c;并且将可序列化类保存成Unity的自定义文件&#xff08;.asset&#xff09;,然后配置自定义文件&#xff08;.asset&#xff09;。 [Serializable][CreateAssetMenu(menuName "ScriptableOject/BuildConfig")]public class BuildC…

一周学会Flask3 Python Web开发-http响应状态码

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 在Flask程序中&#xff0c;客户端发出的请求触发相应的视图函数&#xff0c;获取返回值会作为响应的主体&#xff0c;最后生成…

scratch猜年龄互动小游戏 2024年12月scratch四级真题 中国电子学会 图形化编程 scratch四级真题和答案解析

scratch猜年龄互动小游戏 2024年12月电子学会图形化编程Scratch等级考试四级真题 一、题目要求 老爷爷的年龄是1-100的随机数,老爷爷询问“请猜猜我的年龄是多少?”,输入年龄,老爷爷会回答"大了"或者"小了,直到最后成功猜出年龄。 1、准备工作 (1)删…

跟着 Lua 5.1 官方参考文档学习 Lua (1)

文章目录 1 – Introduction2 – The Language2.1 – Lexical Conventions2.2 – Values and Types2.2.1 – Coercion 1 – Introduction Lua is an extension programming language designed to support general procedural programming with data description facilities. I…

unity学习47:寻路和导航,unity2022后版本如何使用 Navmesh 和 bake

目录 1 寻路和导航对移动的不同 1.1 基础的移动功能 1.1.1 基础移动 1.1.2 智能导航寻路 1.1.3 智能导航寻路还可以 2 如何实现这个效果&#xff1f; 2.1 通过地图网格的形式 2.1.1 警告信息 the static value has been deprecated的对应搜索 2.1.2 新的navigation ba…

达梦存储过程执行后 sql日志信息粗读

如何调试达梦存储过程&#xff1f;快速定位问题 dmgdb 或 manager图形工具 我觉得还可以靠sql日志和DBMS_OUTPUT包&#xff0c;不过最省事的办法放到了最后面&#xff0c;一个sql就能搞清楚了 来段演示代码 set serveroutput on drop table t1; create TABLE t1 (id int, gc…

fpga助教面试题

第一题 module sfp_pwm( input wire clk, //clk is 200M input wire rst_n, input wire clk_10M_i, input wire PPS_i, output reg pwm ) reg [6:0] cunt ;always (posedge clk ) beginif(!rst_n)cunt<0;else if(cunt19) //200M是10M的20倍cunt<0;elsecunt<cunt1;…

【分布式】Hadoop完全分布式的搭建(零基础)

Hadoop完全分布式的搭建 环境准备&#xff1a; &#xff08;1&#xff09;VMware Workstation Pro17&#xff08;其他也可&#xff09; &#xff08;2&#xff09;Centos7 &#xff08;3&#xff09;FinalShell &#xff08;一&#xff09;模型机配置 0****&#xff09;安…

GPT-Sovits:语音克隆训练-遇坑解决

前言 本来以为3050完全无法执行GPT-Sovits训练的&#xff0c;但经过实践发现其实是可以&#xff0c;并且仅花费了十数分钟便成功训练和推理验证了自己的语音模型。 官方笔记&#xff1a;GPT-SoVITS指南 语雀 项目地址&#xff1a;https://github.com/RVC-Boss/GPT-SoVITS 本人…

React之旅-03 路由

做为前端开发框架&#xff0c;React 的组件化设计思想&#xff0c;使前端开发变得更加灵活高效。对于大型复杂的项目来说&#xff0c;页面之间的导航变得尤为重要。因此如何管理路由&#xff0c;是所有开发者必须考虑的问题。 React 官方推荐的路由库-React Router&#xff0c…

Word接入DeepSeek(API的作用)

1.打开”Word”&#xff0c;点击“文件”。 2.点击“选项”。 3.点击“信任中心”——“信任中心设置”。 4. 勾选”启用所有宏“&#xff0c;点击”确定“。 5.点击“自定义功能区”&#xff0c;勾选上“开发工具”&#xff0c;点击“确定”。 6.返回“文件——开发工具“下的…