在golang中实现嵌套接口

3

我有一个接口,其中包含很多子方法。

type InterfaceCheckout interface {
    GetID()    int
    GetItems() []InterfaceCartItem
    // ... then like 30more methods
}

我有一个只使用GetItems方法的方法。
func GetRates(checkoutI InterfaceCheckout) []Rate {
    for _, item := range checkoutI.GetItesm() {
        p := item.GetProduct()
    }
}

我希望能够测试GetRates方法而无需模拟InterfaceCheckout中的所有方法。

我原以为我可以:

  1. create some smaller interfaces that specify only the methods I'm making use of
  2. cast the input as this new interface
  3. pass through to a new internal method

    func GetRates(checkoutI InterfaceCheckout) []Rate {
        getRates(checkoutWrapper(checkoutI))
    }
    
    func getRates(checkoutI checkoutWrapper) []Rate {
        for _, item := range checkoutI.GetItesm() {
            p := item.GetProduct()
        }
    }
    
    // smaller wrapper interfaces
    type checkoutWrapper interface {
        GetItems() []InterfaceCartItem
    }
    
我遇到的问题是,GetItems返回的InterfaceCartItem接口列出了约30个方法,而我只使用其中一个GetProduct。因此,我认为我可以使用相同的解决方案,并创建一个仅包含我需要的一个方法的接口,但是当我尝试更改从checkoutWrapper@GetItems()返回的类型时,golang会说checkoutI不再满足checkoutWrapper接口,因为它返回与GetItems不同的类型,这在技术上是正确的...
我尝试过的代码如下:
func GetRates(checkoutI InterfaceCheckout) []Rate {
    getRates(checkoutWrapper(checkoutI))
}

func getRates(checkoutI InterfaceCheckout) []Rate {
    for _, item := range checkoutI.GetItesm() {
        p := item.GetProduct()
    }
}

// smaller wrapper interfaces
type checkoutWrapper interface {
    GetItems() []itemWrapper
}

type itemWrapper interface {
    GetProduct() InterfaceProduct
}

所以接口方法验证只进行一层深度吗?

你为什么想让 GetProduct 返回一个接口? - Will C
1
Golang 对接口的哲学是让它们尽可能小,因此 30 个方法已经很多了。此外,惯例是在名称末尾使用“-er”来标识接口:InterfaceProduct -> Producter - T. Claverie
1个回答

8
只需将您的界面嵌入虚假对象中即可。 例如:
type InterfaceCheckout interface {
    GetID() int
    GetItems() []InterfaceCartItem
}

type InterfaceCartItem interface {
    GetProduct() string
    GetID() int
}

type fakeCheckout struct {
    InterfaceCheckout
}

func (fakeCheckout) GetItems() []InterfaceCartItem {
    return []InterfaceCartItem{fakeItem{}}
}

type fakeItem struct {
    InterfaceCartItem
}

func (fakeItem) GetProduct() string {
    return "This is the end"
}

func getRates(checkoutI InterfaceCheckout) {
    for _, item := range checkoutI.GetItems() {
        fmt.Printf("%v\n", item.GetProduct())
    }
}

func main() {
    fc := fakeCheckout{}
    getRates(fc)
}

在以下网址查看其实际效果:https://play.golang.org/p/uSFegnZq7S


顺便说一下:避免使用拥有30个方法的接口,否则它们会非常快地变得笨重。


编辑

这行代码有效,但能否解释为什么在结构体中嵌入了接口?

将接口嵌入结构体是有点微妙的。 规范 提到,嵌入一个接口将带入其方法集,因此当调用方法时,它总是被编译。它也会带入接口类型的零成员。

typ := reflect.TypeOf(fc)
fmt.Printf("+%v\n", typ.Field(0))

你可以看到这里有一个成员:
{Name:InterfaceCheckout PkgPath: Type:main.InterfaceCheckout Tag: Offset:0 Index:[0] Anonymous:true}

运行时它是如何工作的?
  • When you call a method that your type overrides1 all is well: your method gets called
  • When you call a method that you don't override, the call goes through to the embedded object which is nil. This panics much like the following would:

    var ic InterfaceCheckout // nil, just like your embedded type
    ic.GetItems()
    

1. 一种类型可以自由地覆盖其嵌入类型所引入的方法


1
这个代码是可以运行的,但是能否解释一下为什么在结构体中嵌入接口会起作用呢?比如在main()函数中,如果我添加fc.GetID(),就会出现panic错误,我本以为会得到函数声明中定义的“零值”。 - AKnox
1
哇,我很高兴看到这可以如此轻松地完成,尽管我曾经认为在 Golang 中遇到了一个丑陋的边缘。我喜欢编写这种语言。(顺便提一下) - AKnox
1
@AKnox,我添加了一个关于这个应该如何工作的解释。 - cnicutar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接