You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Go语言Builder模式中实现编译时客户端配置校验?

解决方案:利用泛型状态标记实现编译期校验

核心思路是通过泛型类型参数标记Builder的状态,仅给已配置客户端(WithClient状态)的Builder提供Do方法,未配置客户端(NoClient状态)的Builder则无此方法,从而在编译阶段阻止非法调用。

具体实现步骤

  1. 定义状态标记类型
    用空结构体作为状态标识,区分未配置客户端和已配置客户端的状态:
type NoClient struct{}
type WithClient struct{}
  1. 定义泛型Builder结构体
    第二个类型参数C用于标记当前状态,初始Builder的状态为NoClient:
import (
    "context"
    "net/http"
    "net/url"
    "encoding/json"
)

type Builder[R any, C any] struct {
    url     string
    query   url.Values
    headers http.Header
    client  *http.Client
}
  1. 初始化方法(返回NoClient状态的Builder)
    Get函数返回未配置客户端的Builder:
func Get[R any](url string) *Builder[R, NoClient] {
    return &Builder[R, NoClient]{
        url:     url,
        query:   make(url.Values),
        headers: make(http.Header),
    }
}
  1. 给NoClient状态的Builder添加配置方法
    除了设置客户端的方法,其他配置方法(如Query、Header)仍返回NoClient状态的Builder,保持链式调用:
func (b *Builder[R, NoClient]) Query(key, value string) *Builder[R, NoClient] {
    b.query.Add(key, value)
    return b
}

func (b *Builder[R, NoClient]) Header(key, value string) *Builder[R, NoClient] {
    b.headers.Add(key, value)
    return b
}
  1. 添加客户端配置方法(转换为WithClient状态)
    Client和UseDefaultClient方法将Builder的状态从NoClient转换为WithClient:
func (b *Builder[R, NoClient]) Client(client *http.Client) *Builder[R, WithClient] {
    return &Builder[R, WithClient]{
        url:     b.url,
        query:   b.query,
        headers: b.headers,
        client:  client,
    }
}

func (b *Builder[R, NoClient]) UseDefaultClient() *Builder[R, WithClient] {
    return &Builder[R, WithClient]{
        url:     b.url,
        query:   b.query,
        headers: b.headers,
        client:  http.DefaultClient,
    }
}
  1. 仅给WithClient状态的Builder添加Do方法
    这样只有配置过客户端的Builder才能调用Do:
func (b *Builder[R, WithClient]) Do(ctx context.Context) (*R, error) {
    // 构建请求并发送的逻辑
    req, err := http.NewRequestWithContext(ctx, http.MethodGet, b.url, nil)
    if err != nil {
        return nil, err
    }
    req.URL.RawQuery = b.query.Encode()
    req.Header = b.headers

    resp, err := b.client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var result R
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return nil, err
    }
    return &result, nil
}

效果验证

  • 合法调用(编译通过):
type ResponseType struct {
    // 自定义响应结构
}

// 使用自定义客户端
resp1, err := Get[ResponseType]("https://example.com").
    Query("foo", "bar").
    Header("Accept", "text/plain").
    Client(&http.Client{}).
    Do(context.Background())

// 使用默认客户端
resp2, err := Get[ResponseType]("https://example.com").
    Query("foo", "bar").
    Header("Accept", "text/plain").
    UseDefaultClient().
    Do(context.Background())
  • 非法调用(编译失败):
// 未配置客户端,编译时会提示:Do未定义在*Builder[ResponseType, NoClient]类型上
resp, err := Get[ResponseType]("https://example.com").
    Query("foo", "bar").
    Do(context.Background())

这种方案既保留了你想要的链式调用写法,又能在编译阶段强制校验客户端配置,完全符合需求。

内容的提问来源于stack exchange,提问作者betabandido

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 10:38:12