Golang中如何处理嵌入类型的流式接口
Golang中如何处理嵌入类型的流式接口 假设我有一个实现了流式接口的第三方类型,即调用指针上的函数会更新状态并返回该指针:
type ThirdPartyType struct {}
func NewThirdPartyType() *ThirdPartyType {
return &ThirdPartyType{}
}
func (tpt *ThirdPartyType) Foo(foo string) *ThirdPartyType {
// ...
return tpt
}
使用这种类型很简洁:
tpt := NewThirdPartyType().Foo("foo")
我想包装这个 ThirdPartyType 并添加一些新方法。为此,我将其嵌入到 MyType 中,如下所示:
type MyType struct {
*ThirdPartyType
}
func NewMyType() *MyType {
return &MyType{}
}
func (mt *MyType) Bar(bar string) *MyType {
// ...
return mt
}
虽然对于我的自定义方法,我可以保持流式风格,但对于那些从 ThirdPartyType “继承”的方法则不行,因为它们返回 *ThirdPartyType,因此不能在链式调用中使用:
var mt *MyType = NewMyType().Bar("bar")
mt.Foo("foo")
这相当不幸,因为它破坏了我之前的简洁性。到目前为止,我找到的唯一解决方案是在 MyType 上重新实现 ThirdPartyType 的方法,例如:
func (mt *MyType) Foo(foo string) *MyType {
mt.Foo(foo)
return mt
}
现在我实际上可以这样做:
mt := NewMyType().Bar("bar").Foo("foo")
然而,这需要相当多的样板代码。另外,我需要重新实现的方法数量大约有50个,所以我宁愿不手动维护这些。
我想出的最佳解决方案是使用代码生成结合 reflect 来自动处理这个问题。我只是想确认一下,在我走这条路之前,是否遗漏了更简单的解决方案。
更多关于Golang中如何处理嵌入类型的流式接口的实战教程也可以访问 https://www.itying.com/category-94-b0.html
你好。遗憾的是,据我所知,这个问题没有解决方案。深入探究的话,我认为这更多是关于实现的设计问题。具体来说,我不喜欢方法链式调用,并尽可能在 Go 中避免使用它,更倾向于 functional options 模式。如果你坚持要使用链式调用,我建议你查看 gorm 的源代码。我从未见过比这个包更好的链式调用实现。
更多关于Golang中如何处理嵌入类型的流式接口的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Golang中处理嵌入类型的流式接口确实存在这个限制。当嵌入类型的方法返回*ThirdPartyType时,链式调用会中断。以下是几种解决方案:
1. 方法包装(当前方案)
你目前的方案是可行的,但需要大量样板代码。可以简化为:
func (mt *MyType) Foo(foo string) *MyType {
mt.ThirdPartyType.Foo(foo)
return mt
}
2. 使用接口转换
创建接口并转换返回类型:
type ThirdPartyInterface interface {
Foo(string) *ThirdPartyType
// 其他方法...
}
func (mt *MyType) Foo(foo string) *MyType {
var iface ThirdPartyInterface = mt.ThirdPartyType
iface.Foo(foo)
return mt
}
3. 代码生成方案
对于50个方法,代码生成是最实际的方案。使用go:generate指令:
//go:generate go run generate_methods.go
type MyType struct {
*ThirdPartyType
}
func NewMyType() *MyType {
return &MyType{ThirdPartyType: NewThirdPartyType()}
}
创建生成器generate_methods.go:
// +build ignore
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"strings"
)
func main() {
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, "third_party.go", nil, parser.ParseComments)
if err != nil {
panic(err)
}
var methods []string
ast.Inspect(node, func(n ast.Node) bool {
fn, ok := n.(*ast.FuncDecl)
if !ok || fn.Recv == nil {
return true
}
// 检查是否返回 *ThirdPartyType
if results := fn.Type.Results; results != nil && len(results.List) == 1 {
if star, ok := results.List[0].Type.(*ast.StarExpr); ok {
if ident, ok := star.X.(*ast.Ident); ok && ident.Name == "ThirdPartyType" {
methods = append(methods, fn.Name.Name)
}
}
}
return true
})
// 生成包装方法
out, _ := os.Create("mytype_wrappers.go")
defer out.Close()
fmt.Fprintln(out, "package main")
fmt.Fprintln(out)
for _, method := range methods {
fmt.Fprintf(out, "func (mt *MyType) %s(s string) *MyType {\n", method)
fmt.Fprintf(out, " mt.ThirdPartyType.%s(s)\n", method)
fmt.Fprintf(out, " return mt\n")
fmt.Fprintf(out, "}\n\n")
}
}
4. 使用反射的动态包装
如果不想生成代码,可以使用反射动态创建包装器:
import "reflect"
type MyType struct {
*ThirdPartyType
wrappers map[string]func(string) *MyType
}
func NewMyType() *MyType {
mt := &MyType{
ThirdPartyType: NewThirdPartyType(),
wrappers: make(map[string]func(string) *MyType),
}
mt.initWrappers()
return mt
}
func (mt *MyType) initWrappers() {
t := reflect.TypeOf(mt.ThirdPartyType)
for i := 0; i < t.NumMethod(); i++ {
method := t.Method(i)
if method.Type.NumIn() == 2 && method.Type.NumOut() == 1 {
mt.createWrapper(method.Name)
}
}
}
func (mt *MyType) createWrapper(name string) {
mt.wrappers[name] = func(s string) *MyType {
reflect.ValueOf(mt.ThirdPartyType).MethodByName(name).Call([]reflect.Value{
reflect.ValueOf(s),
})
return mt
}
}
// 使用方法
func (mt *MyType) MethodByName(name string) func(string) *MyType {
return mt.wrappers[name]
}
// 使用示例
mt := NewMyType()
mt.MethodByName("Foo")("foo").Bar("bar")
5. 类型断言链式调用
如果第三方类型的方法不多,可以创建一个辅助函数:
func (mt *MyType) WithThirdParty(fn func(*ThirdPartyType) *ThirdPartyType) *MyType {
fn(mt.ThirdPartyType)
return mt
}
// 使用
mt := NewMyType().
Bar("bar").
WithThirdParty(func(t *ThirdPartyType) *ThirdPartyType {
return t.Foo("foo").Baz("baz")
}).
Bar("another")
对于50个方法的情况,代码生成是最佳选择,因为它:
- 类型安全
- 编译时检查
- 性能最好
- 维护成本低
建议使用go:generate配合模板或AST解析来自动生成包装方法,这样可以确保与第三方库同步更新。

