使用golang嵌套结构体(带有接口)

huangapple go评论81阅读模式
英文:

Using golang nesting structs (with interfaces)

问题

简而言之,众所周知,为了实现良好的架构,我们应该使用接口来描述其行为。Golang实现了这个想法,但它们的接口只有方法,没有字段。因此,使用它们的唯一方法就是创建getter和setter。但是,我在指针方面遇到了问题。

例如:


package main

import "fmt"

// A

type IA interface {
    Foo() string
}

type A struct {
    foo string
}

func (a *A) Foo() string {
    return a.foo
}

// B

type IB interface {
    A() *IA
}

type B struct {
    a *IA
}

func (b *B) A() *IA {
    return b.a
}

// main

func main() {
    a := &A{"lol"}
    b := &B{a} // cannot use a (type *A) as type *IA in field value: *IA is pointer to interface, not interface

    foo := b.A().Foo() // b.A().Foo undefined (type *IA is pointer to interface, not interface)
    fmt.Println(foo)
}


当然,我可以使用类似这样的代码:

(*(*b).A()).Foo()

但这样好吗?是否合适?

我只是想要像Python、JavaScript、TypeScript那样的行为:

someObj.child1.child2.child2SomeMethod()

也许我在指针方面搞混了,我只是想知道在Golang中处理嵌套对象的方式。

英文:

In two words, it's not a secret for anyone that for a good architecture we should use interfaces, those describe it's behavior. Golang implemented this idea, but their interfaces has only methods, but not fields. So, the only way to use them is like creating getters and setters. But with that I've got a problem with pointers.

For example:


package main

import "fmt"

// A

type IA interface {
    Foo() string
}

type A struct {
    foo string
}

func (a *A) Foo() string {
    return a.foo
}

// B

type IB interface {
    A() *IA
}

type B struct {
    a *IA
}

func (b *B) A() *IA {
    return b.a
}

// main

func main() {
    a := &A{"lol"}
    b := &B{a} // cannot use a (type *A) as type *IA in field value: *IA is pointer to interface, not interface

    foo := b.A().Foo() // b.A().Foo undefined (type *IA is pointer to interface, not interface)
    fmt.Println(foo)
}


Ofc, I can use something like this:

(*(*b).A()).Foo()

but will this be that good and appropriate?

I just wanna have behavior like in python, js, ts:

someObj.child1.child2.child2SomeMethod()

Maybe I messed up with pointers, I just wanna know the golang way to work with nested objects.

答案1

得分: 1

这是一个常见的问题,特别是对于那些在高级语言中有经验的新手来说。以下是我如何区分它们的方法:

首先,让我们确定在Go中什么是“接收器”(例如“方法”)。与Python类似,方法实际上与类型相关联是一种语法糖。考虑以下示例:

package main

import "fmt"

type F struct {
  i int
}

func (f F)Foo() {
  fmt.Println(f.i)
}

func main() {
  F.Foo(F{1})
}

令人惊讶的是,这段代码可以编译,并成功打印出预期的1。这是因为当您在类型上调用接收器时,类型实际上成为接收器的第一个参数。

让我们快速回顾一下指针,因为您在评论中似乎把它们弄反了。所以只是为了明确:计算机程序中的任何都存储在内存中,并且它在内存中的地址也是一个可以存储在变量中的值。我们将这些变量称为指向该值的“指针”。

如果我将一个传递给函数,它们只能在其函数范围内更改该值。如果该值是内存中的地址,情况也是如此。但是,我可以更改该地址上的数据,从而影响具有函数范围之外的作用域的数据。

package main
import "fmt"

func f(i int) { i = i + 2 }

func pf(i *int) { *i = *i + 2 }

var i = 1

func main() {
  f(i)
  fmt.Println(i)
  pf(&i)
  fmt.Println(i)
}

输出结果为

1
3

f(i)更改了其对i的本地副本,但pf(&i)更改了存储在i中的地址上的数据。

为什么我要讲这些?因为这就是为什么大多数Go中的接收器都是指针接收器的原因;因为您不希望传递接收器的副本;实际上,您希望传递接收器的地址,以便它可以在自己的方法中进行自我修改。

请记住,接收器是语法糖:

func (t *Type)f(arg string)

等同于:

func f(t *Type, arg string)

希望这样清楚地解释了为什么指针接收器如此常见!好了,接下来是接口的一面。

如您所知,接口定义了一组方法。如果类型定义了具有相同签名的方法,则该类型满足该接口。这对于值接收器或指针接收器都成立。

但是,一个类型不能同时具有具有相同名称的值接收器和指针接收器:

func (t  T)F() {}
func (t *T)F() {} //method redeclared: T.F

这意味着类型和指向该类型的指针不能具有相同的接收器;因此,类型或指向类型的指针实现了接收器,但不能同时实现。这一点很容易被忽视,因为Go会自动转换。所以这样是可以的:

package main

type T struct{}

func (t  T)F() {}
func (t *T)PF() {}

func main() {
 var t T 
 t.F()
 t.PF()
}

t.PF()中,t会自动转换为指针。

但是要重申一下,类型和指向类型的指针不能同时定义相同的接收器。因此,如果T满足接口I,则*T不满足,反之亦然。

话虽如此,很容易得出一个简单的规则:当您想要使用指针满足接口时,永远不要取接口的指针

在您的代码中,*A满足IA。因此,您可以说您的IA是“真正的*A”。以这种方式思考,您会发现对一个实际上是*AIA取地址是没有意义的。

将所有这些内容综合起来,这是定义两个接口并链接调用的示例。请注意,虽然我的结构体的指针可能是满足接口的值,但我永远不需要取接口的地址。对于IFIG接口的使用者来说,它们是类型还是指针接收器并不重要。

package main

import "fmt"

type IF interface {
  G() IG
}

type F struct {
  g IG
}

func (f *F)G() IG {
  return f.g
}

type IG interface {
  G()
}

type G struct{
  i int
}

func (g *G)G() {
  g.i++
  fmt.Println("Gee, ", g.i)
}

func main() {
  f := F{&G{1}}
  f.G().G()
}

很少需要指向接口的指针,所以请确保您考虑的是“由指针满足的接口”而不是“指向接口的指针”。

英文:

This is a common point to get tripped up, especially for folks new to go with backgrounds in higher level languages. Here's how I keep it straight:

First of all, let's identify what a "receiver" (eg "method") is in Go. Much like python, the fact that a method is actually connected to a type is syntactic sugar. Consder this example:

package main

import "fmt"

type F struct {
  i int
}

func (f F)Foo() {
  fmt.Println(f.i)
}

func main() {
  F.Foo(F{1})
}

Surprising as it may be, this code compiles and successuflly prints out the expected 1. That's because what really happens when you call a receiver on a type, is that the type becomes the first argument of the receiver.

Really quick let's review pointers too, as you seem to have said it backwards in the comments. So just to be clear: any value in a computer program is stored in memory, and it's address in memory is also a value that can be stored in a variable. We call these variables "pointers to" the value.

If I pass a function a value, they can only change that value within the scope of their function. If that value is an address in memory, the same thing is true. but, I can change the data at that address, thus affecting data that has a scope outside the function.

package main
import "fmt"

func f(i int) { i = i + 2 }

func pf(i *int) { *i = *i + 2 }

var i = 1

func main() {
  f(i)
  fmt.Println(i)
  pf(&i)
  fmt.Println(i)
}

Prints out

1
3

f(i) changes its local copy of i , but pf(&i) changes the data at the address stored in i.

Why did I go through all that? Because that's the reason most receivers in go are pointer receivers; because you don't want to pass a copy of the receiver; you actally want to pass the address of the receiver so that it can mutate itself within its own method.

Keep in mind receivers are syntactic sugar:

func (t *Type)f(arg string)

is equivalent to:

func f(t *Type, arg string)

Hopefully that makes it clear why pointer receivers are so common! Okay, on to the interface side of things.

An interface, as you know, defines a set of methods. A type satisfies that interface if it defines methods with the same signatures. This can be true of value receivers or pointer receivers.

However, a type cannot have both value and pointer receivers with the same name:

func (t  T)F() {}
func (t *T)F() {} //method redeclared: T.F

So that means that a type and a pointer to the type cannot have the same receiver; thus either the type or a pointer to the type implements the receiver, but not both. This point is easy to overlook because go will automatically convert. So this works fine:


type T struct{}

func (t  T)F() {}
func (t *T)PF() {}

func main() {
 var t T 
 t.F()
 t.PF()
}

in t.PF(), t is automatically converted to a pointer.

But to reiterate, a type and a pointer to type cannot both define the same receiver. So if T satisfies interface I, *T does not, and vise versa.

That having been said and understood, it's easy to come up with an easy rule: never take a pointer to an interface when you mean to satisfy an interface with a pointer.

In your code, *A satisfies IA. So you could say your IA is "really an *A. Thinking of it like this, you can see that taking the address of an IA that is actually an *A` doesn't make any sense.

Putting it all together, here's the definition two interfaces and chaining the call. Note that while pointers to my structs may be the value satisfying the interface, I never need to take the address of an interface. To the consumer of IF and IG interfaces, whether they are types or pointer receivers are not relevant.

package main

import "fmt"

type IF interface {
  G() IG
}

type F struct {
  g IG
}

func (f *F)G() IG {
  return f.g
}

type IG interface {
  G()
}

type G struct{
  i int
}

func (g *G)G() {
  g.i++
  fmt.Println("Gee, ", g.i)
}

func main() {
  f := F{&G{1}}
  f.G().G()
}

It's very uncommon to need a pointer to an interface, so make sure you think "interface satisfied by pointer" and not "pointer to interface".

huangapple
  • 本文由 发表于 2021年10月13日 18:38:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/69554000.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定