【问题标题】:Using golang nesting structs (with interfaces)使用 golang 嵌套结构(带接口)
【发布时间】:2023-03-04 09:49:01
【问题描述】:

简而言之,对于任何人来说,对于一个好的架构我们应该使用接口,这不是什么秘密,那些描述了它的行为。 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)
}


Ofc,我可以使用这样的东西:

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

但这会那么好和合适吗?

我只想在 python、js、ts 中有类似的行为:

someObj.child1.child2.child2SomeMethod()

也许我搞砸了指针,我只是想知道 golang 处理嵌套对象的方式。

【问题讨论】:

  • *IA 是你的问题。不要使用指向接口的指针,除非它正是您所需要的,几乎从来都不是这样。解决方案?请改用IAplay.golang.org/p/j_JXS3uDX4U
  • @mkopriva,我认为我们应该使用指针,只要有可能不会错误地对其值应用任何更改
  • 我不确定你从哪里得到的启发式方法,但它是错误的,不管我们是否在谈论接口。
  • @dominux 实际上相反是正确的:按值传递不允许更改值(假设结构中没有任何嵌套指针)。请参阅stackoverflow.com/questions/23542989/… 了解更多信息。

标签: go architecture nested


【解决方案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。那是因为当你调用一个类型的接收者时,真正发生的事情是,类型成为接收者的第一个参数。

真的很快,让我们也回顾一下指针,正如您在 cmets 中所说的那样。所以要明确一点:计算机程序中的任何都存储在内存中,它在内存中的地址也是可以存储在变量中的值。我们称这些变量为“指向”值的“指针”。

如果我向函数传递一个,它们只能在其函数范围内更改该值。如果该值是内存中的地址,则同样如此。但是,我可以更改 该地址处的数据,从而影响具有函数范围之外的数据。

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 会自动转换。所以这很好用:


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. Thinking of it like this, you can see that taking the address of an IA that is actually an *A`没有任何意义。

总而言之,这里定义了两个接口并链接调用。请注意,虽然指向我的结构的指针可能是满足接口的值,但我永远不需要获取接口的地址。对于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()
}

需要指向接口的指针并不常见,因此请确保您认为“接口满足指针”而不是“指向接口的指针”。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-10-15
    • 2021-12-24
    • 2015-05-16
    • 1970-01-01
    • 2013-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多