如何消除Go代码中APIData方法的冗余实现?
问题描述
现有如下Go代码,用于调用不同API接口并解析返回的JSON响应:
type Collections struct { Object string `json:"object"` Data []struct { Object string `json:"object"` ID string `json:"id"` Groups interface{} `json:"groups"` ExternalID interface{} `json:"externalId"` } `json:"data"` } type Members struct { Object string `json:"object"` Data []struct { Object string `json:"object"` ID string `json:"id"` UserID string `json:"userId"` Name string `json:"name"` Email string `json:"email"` } `json:"data"` } type Groups struct { Object string `json:"object"` Data []struct { Object string `json:"object"` ID string `json:"id"` Name string `json:"name"` AccessAll bool `json:"accessAll"` ExternalID string `json:"externalId"` } `json:"data"` } func bwRequestWithBT(uri string) []byte { url := endpoint + uri // Create a new request using http req, err := http.NewRequest("GET", url, nil) // Send req using http Client client := &http.Client{} resp, err := client.Do(req) if err != nil { log.Err(err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) return body } func (c *Collections) APIData() { err := json.Unmarshal(RequestWithBT("/collections"), &c) if err != nil { log.Error().Err(err).Msg("Error while unmarshalling collection") } } func (m *Members) APIData() { err := json.Unmarshal(RequestWithBT("/members"), &m) if err != nil { log.Error().Err(err).Msg("Error while unmarshalling members") } } func (g *Groups) APIData() { err := json.Unmarshal(RequestWithBT("/groups"), &g) if err != nil { log.Error().Err(err).Msg("Error while unmarshalling groups") } } var members Members members.APIData() var groups Groups groups.APIData() var collection Collections collection.APIData()
当前三个结构体各自实现了APIData方法,逻辑重复冗余。希望实现类似如下示例的通用APIData方法:
func (object GenericObject) APIData(uri string){ err := json.Unmarshal(RequestWithBT(uri), &object) if err != nil { log.Error().Err(err).Msg("Error while unmarshalling") } } var members Members var groups Groups var collection Collections members.APIData("/members") groups.APIData("/groups") collection.APIData("/collections")
请问这种实现方式是否可行?
解决方案
你给出的GenericObject写法不可行,因为Go是静态类型语言,没有这种"通用对象"的隐式适配机制。不过可以通过两种方式实现通用的APIData逻辑:
方式1:使用泛型函数(Go 1.18+推荐)
直接编写一个泛型函数,接收任意结构体指针和API路径,完成请求与解析:
func FetchAPIData[T any](obj *T, uri string) error { body := bwRequestWithBT(uri) err := json.Unmarshal(body, obj) if err != nil { log.Error().Err(err).Msgf("Error while unmarshalling %T", obj) return err } return nil }
调用方式如下:
var members Members FetchAPIData(&members, "/members") var groups Groups FetchAPIData(&groups, "/groups") var collection Collections FetchAPIData(&collection, "/collections")
方式2:利用接口与指针接收者实现方法复用
如果不想用泛型,可以定义一个空接口,然后为所有结构体实现统一签名的方法,内部调用封装好的重复逻辑:
// 辅助函数,封装重复的请求与解析逻辑 func fetchAndUnmarshal(obj interface{}, uri string) error { body := bwRequestWithBT(uri) err := json.Unmarshal(body, obj) if err != nil { log.Error().Err(err).Msg("Error while unmarshalling") return err } return nil } // 为每个结构体实现APIData方法,内部调用辅助函数 func (c *Collections) APIData(uri string) error { return fetchAndUnmarshal(c, uri) } func (m *Members) APIData(uri string) error { return fetchAndUnmarshal(m, uri) } func (g *Groups) APIData(uri string) error { return fetchAndUnmarshal(g, uri) }
调用方式和你期望的一致:
var members Members members.APIData("/members") var groups Groups groups.APIData("/groups") var collection Collections collection.APIData("/collections")
补充说明
原代码中bwRequestWithBT存在两处风险:
resp可能为nil时调用resp.Body.Close()会触发panic,需要先判断请求错误;io.ReadAll的错误被忽略,会隐藏读取响应体的问题。
优化后的bwRequestWithBT:
func bwRequestWithBT(uri string) ([]byte, error) { url := endpoint + uri req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } client := &http.Client{} resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() return io.ReadAll(resp.Body) }
对应的泛型函数也需要调整为处理错误:
func FetchAPIData[T any](obj *T, uri string) error { body, err := bwRequestWithBT(uri) if err != nil { log.Error().Err(err).Msg("Failed to fetch API data") return err } err = json.Unmarshal(body, obj) if err != nil { log.Error().Err(err).Msgf("Error while unmarshalling %T", obj) return err } return nil }
内容的提问来源于stack exchange,提问作者Nagri
相关产品推荐
相关产品推荐

