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

2 回复

你好。遗憾的是,据我所知,这个问题没有解决方案。深入探究的话,我认为这更多是关于实现的设计问题。具体来说,我不喜欢方法链式调用,并尽可能在 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个方法的情况,代码生成是最佳选择,因为它:

  1. 类型安全
  2. 编译时检查
  3. 性能最好
  4. 维护成本低

建议使用go:generate配合模板或AST解析来自动生成包装方法,这样可以确保与第三方库同步更新。

回到顶部