【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 main

import (
	"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 = &port
		return 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 int
	if options.port == nil {
		// 未设置端口,使用默认值
		port = defaultHTTPPort
	} else {
		if *options.port == 0 {
			// 端口为0,使用随机端口
			port = randomPort()
		} else {
			// 使用指定的端口
			port = *options.port
		}
	}

	_ = port
	return 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 := *c
    if 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 = tweakListOptions
        return 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) error
    Find(id string) (*User, error)
}

type UserService struct {
    repo userRepository  // 在使用处定义接口
}
  1. Function Options 的特点
// 选项定义在包的实现端
type Option func(*options) error

func WithPort(port int) Option {
    return func(o *options) error {
        o.port = &port
        return 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 server

type Option func(*options) error

func WithPort(port int) Option { ... }
func WithTimeout(t time.Duration) Option { ... }

// 客户端使用(简洁清晰)
package client

func 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    *int
    timeout *time.Duration
}

type Option func(*options) error

func WithPort(port int) Option {
    return func(o *options) error {
        if port < 0 {
            return errors.New("invalid port")
        }
        o.port = &port
        return nil
    }
}

func NewServer(addr string, opts ...Option) (*Server, error) {
    var options options
    for _, 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    *int
    timeout *time.Duration
}

type Option func(*options) error

func WithPort(port int) Option {
    return func(o *options) error {
        if port < 0 {
            return errors.New("port must be positive")
        }
        o.port = &port
        return 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 模式当:

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【单臂路由配置】

【单臂路由配置】 设备接口IP子网网关vlanR1G0/0/1.1192.168.1.254255.255.255.0NAvlan10R1G0/0/1.2192.168.2.254255.255.255.0NAvlan20R1G0/0/1.3192.168.3.254255.255.255.0NAvlan30PC1e0/0/1192.168.1.1255.255.255.0192.168.1.254vlan10PC2e0/0/1192.168.2.1255.255.255.0…

NutUI内网离线部署

文章目录 官网拉取源代码到本地仓库修改源代码打包构建nginx反向代理部署访问内网离线地址 在网上找了一圈没有写NutUI内网离线部署的文档&#xff0c;花了1天时间研究下&#xff0c;终于解决了。 对于有在内网离线使用的小伙伴就可以参考使用了 如果还是不会联系UP主:QQ:10927…

【Linux AnolisOS】关于Docker的一系列问题。尤其是拉取东西时的网络问题,镜像源问题。

AnolisOS 8中使用Docker部署&#xff08;全&#xff09;_anolis安装docker-CSDN博客 从在虚拟机安装龙蜥到安装docker上面这篇文章写的很清晰了&#xff0c;我重点讲述我解决文章里面问题一些的方法。 问题1&#xff1a; docker: Get https://registry-1.docker.io/v2/: net/h…

免费体验,在阿里云平台零门槛调用满血版DeepSeek-R1模型

一、引言 随着人工智能技术的飞速发展&#xff0c;各类AI模型层出不穷。其中&#xff0c;DeepSeek作为一款新兴的推理模型&#xff0c;凭借其强大的技术实力和广泛的应用场景&#xff0c;逐渐在市场中崭露头角。本文将基于阿里云提供的零门槛解决方案&#xff0c;对DeepSeek模…

ARM Linux平台下 OpenCV Camera 实验

一、硬件原理 1. OV2640 1.1 基本功能 OV2640 是一款低功耗、高性能的图像传感器&#xff0c;支持以下功能&#xff1a; 最高分辨率&#xff1a;200 万像素&#xff08;1600x1200&#xff09;。 输出格式&#xff1a;JPEG、YUV、RGB。 内置图像处理功能&#xff1a;自动曝…

论文笔记-WSDM2025-ColdLLM

论文笔记-WSDM2025-Large Language Model Simulator for Cold-Start Recommendation ColdLLM&#xff1a;用于冷启动推荐的大语言模型模拟器摘要1.引言2.前言3.方法3.1整体框架3.1.1行为模拟3.1.2嵌入优化 3.2耦合漏斗ColdLLM3.2.1过滤模拟3.2.2精炼模拟 3.3模拟器训练3.3.1LLM…

后端开发:开启技术世界的新大门

在互联网的广阔天地中&#xff0c;后端开发宛如一座大厦的基石&#xff0c;虽不直接与用户 “面对面” 交流&#xff0c;却默默地支撑着整个互联网产品的稳定运行。它是服务器端编程的核心领域&#xff0c;负责处理数据、执行业务逻辑以及与数据库和其他后端服务进行交互。在当…

如何查看java的字节码文件?javap?能用IDEA吗?

编译指令&#xff1a; javac YourProject.java 查看字节码文件的指令&#xff1a; javap -c -l YourProject.class 不添加-c指令就不会显示字节码文件&#xff1a; 不添加 -l 就不会显示源代码和字节码文件的对应关系&#xff1a; 添加-l之后多出来这些&#xff1a; IDEA不太…

Linux-GlusterFS配置

文章目录 GlusterFS配置 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Linux专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2025年02月18日19点21分 GlusterFS配置 1、分区操作 fdisk -l #查看本地磁盘 fdisk /dev/vdb #对/dev/vdb进…

[C语言]指针进阶压轴题

下面代码打印结果是什么&#xff1f; #include<stdio.h> int main() {char* c[] { "ENTER","NEW","POINT","FIRST" };char** cp[] { c 3,c 2,c 1,c };char*** cpp cp;printf("%s\n", **cpp);printf("%s\n…

DeepSeek 助力 Vue 开发:打造丝滑的点击动画(Click Animations)

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

SpringBoot论坛网站 – 功能详解与部署教程

项目概述 《SpringBoot的论坛网站》是一个基于SpringBoot框架开发的现代化论坛平台&#xff0c;旨在为用户提供一个便捷的交流空间。该项目不仅功能丰富&#xff0c;还具备良好的扩展性和易用性&#xff0c;适合用于学习、分享和讨论各类话题。以下是项目的核心功能模块和部署…

SpringSecurity初始化的本质

一、对SpringSecurity初始化的几个疑问 通过前面第一次请求访问的分析我们明白了一个请求就来后的具体处理流程 对于一个请求到来后会通过FilterChainProxy来匹配一个对应的过滤器链来处理该请求。那么这里我们就有几个疑惑。 FilterChainProxy什么时候创建的?过滤器链和对应的…

【大模型系列篇】DeepSeek-R1如何通过强化学习有效提升大型语言模型的推理能力?

如何通过强化学习&#xff08;RL&#xff09;有效提升大型语言模型&#xff08;LLM&#xff09;的推理能力&#xff1f; 《DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning》由DeepSeek-AI团队撰写&#xff0c;主要介绍了他们开发的第一代…

无缝对接[系列2]:在VSCode中继续接入本地DeepSeek的完整指南---实现代码协助编写~

无缝对接&#xff1a;在VSCode中继续接入本地DeepSeek的完整指南 在上一篇文章中&#xff0c;我们已经详细介绍了如何成功部署本地 DeepSeek&#xff0c;使得开发者能够在本地环境中高效地进行深度学习和数据分析工作。 部署完成后&#xff0c;如何在 Visual Studio Code (VS…

linux5-多任务--进程fork()

一.多任务&#xff1a;让系统具备同时处理多个任务的能力 1.如何实现多任务 1.1进程&#xff1a;操作系统上正在运行的程序&#xff0c;需要消耗内存和CPU 1.1.1 进程的生存周期&#xff1a;创建、调度、消亡 1.1.1.1进程的创建&#xff1a;每个进程被创建时&#xff0c;操作…

原生稀疏注意力机制(NSA):硬件对齐且可原生训练的稀疏注意力机制-论文阅读

摘要 长上下文建模对于下一代语言模型至关重要&#xff0c;但标准注意力机制的高计算成本带来了巨大的计算挑战。稀疏注意力提供了一种在保持模型能力的同时提高效率的有前途的方向。本文提出了一种名为 NSA&#xff08;原生可训练稀疏注意力机制&#xff09; 的方法&#xff…

C++ Primer 库-IO类

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

FlutterAssetsGenerator插件的使用

在Plugins中找到FlutterAssetsGenerator插件&#xff0c;点击安装。 更改生成的资源索引类可以修改名字。 在根目录下创建assets/images文件夹&#xff0c;用于存储图片。 点击images文件夹&#xff0c;鼠标右键点击Flutter&#xff1a;Configuring Paths&#xff0c;pub…

网络安全与防范

1.重要性 随着互联网的发达&#xff0c;各种WEB应用也变得越来越复杂&#xff0c;满足了用户的各种需求&#xff0c;但是随之而来的就是各种网络安全的问题。了解常见的前端形式和保护我们的网站不受干扰是我们每个优秀fronter必备的技能。 2.分类 XSS干扰 CSRF干扰 网络劫持干…