如何在Go语言Builder模式中实现编译时客户端配置校验?
解决方案:利用泛型状态标记实现编译期校验
核心思路是通过泛型类型参数标记Builder的状态,仅给已配置客户端(WithClient状态)的Builder提供Do方法,未配置客户端(NoClient状态)的Builder则无此方法,从而在编译阶段阻止非法调用。
具体实现步骤
- 定义状态标记类型
用空结构体作为状态标识,区分未配置客户端和已配置客户端的状态:
type NoClient struct{} type WithClient struct{}
- 定义泛型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 }
- 初始化方法(返回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), } }
- 给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 }
- 添加客户端配置方法(转换为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, } }
- 仅给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
相关产品推荐
相关产品推荐

