【问题标题】:Non-interface methods in interface implementation接口实现中的非接口方法
【发布时间】:2019-01-08 11:14:33
【问题描述】:

我有一个定义方法的接口。我有一个实现这个接口的结构。在其中,我已经实现了该接口的方法,并且还定义了其他方法。

例如:

package main

import (
    "fmt"
)   

type Animal interface {
    MakeNoise()
}

type Dog struct {
    color string
}

/* Interface implementation */

func (d *Dog) MakeNoise() {
    fmt.Println("Bark!")
}

/* End Interface implementation */

func (d *Dog) WagTail() {
    fmt.Println(d.color + " dog: Wag wag")
}

func NewDog(color string) Animal {
    return &Dog{color}
}

func main() {
    dog := NewDog("Brown")
    dog.MakeNoise()
    dog.WagTail()

}

在操场上:https://play.golang.org/p/B1GgoNToNl_l

这里,WagTail() 不是 Animal 接口的一部分,而是属于 Dog 结构体。运行这段代码会报错

dog.WagTail 未定义(Animal 类型没有 WagTail 字段或方法)。

有没有一种方法可以让结构遵循接口并定义它自己的方法?

【问题讨论】:

  • 这会让 Dog 遵守 Animal 界面吗? Dog 和 Animal 之间的关系如何执行?
  • 所以我需要做var _ Animal = (*Dog)(nil) 吗?根据该线程中的 cmets ,这是强制实现接口方法的非官方方式。这对我的目的有用。但是想知道是否有更好的方法来完成我的场景?我的代码示例是好代码吗?
  • 代码会编译。请看操场:play.golang.org/p/IRmreX4nhYt.

标签: oop go


【解决方案1】:

这可能会对你有所帮助。

d := dog.(*Dog)
d.WagTail()

在操场上:https://play.golang.org/p/KlNqpmvFTJi

【讨论】:

  • 这似乎很有用。所以这是把 Animal 变成 Dog 对吧?
  • @Bharat 不,这不是将Animal 接口转换为Dog。这只是使用类型断言来获取 Dog 的底层值。但是为 struct 创建一个新实例然后使用它来调用方法与创建 Dog 实例并直接使用它是一样的,而不是从 Animal 接口的基础值创建 struct 实例
  • 感谢您的解释。 d := dog.(*Dog) 相当于 d := &Dog{} 对吧?
  • @Bharat 没错,两者都在创建一个结构 Dog 的实例,该实例将用于调用以 Dog 结构为接收者的方法。
  • @Bharat,@Himanshu 我认为d := dog.(*Dog) 不会创建结构Dog 的实例。 ddog 指向同一个地址。在操场上:play.golang.org/p/eWBawUhhy22
【解决方案2】:

错误描述了一切:

dog.WagTail 未定义(Animal 类型没有 WagTail 字段或方法)

要实现接口,您应该实现其中定义的所有方法。

dog := NewDog("Brown")
dog.MakeNoise()
dog.WagTail()

现在NewDog 返回包含MakeNoise 方法但不包含WagTail 的Animal 接口。

管理您的需求的唯一方法是创建结构类型Dog 的变量,然后您可以调用任何以 Dog 作为接收者的方法。

d := &Dog{"Brown"}
d.WagTail()

或者您可以从 NewDog 方法返回指向 Dog 结构的指针,就像您在注释中提到的代码中所做的那样:

func NewDog(color string) *Dog {
    return &Dog{color}
}

但是如果接口中没有定义方法,则不能使用结构体作为方法接收器来实现它。

Golang 提供了一种方式:

您可以要求编译器检查类型 T 是否实现了 通过尝试使用 T 的零值或 指向 T 的指针,视情况而定

type T struct{}
var _ I = T{}       // Verify that T implements I.
var _ I = (*T)(nil) // Verify that *T implements I.

如果 T(或 *T,相应地)没有实现 I,错误将在编译时被捕获。

如果您希望界面的用户明确声明他们 实现它,您可以将具有描述性名称的方法添加到 接口的方法集。例如:

type Fooer interface {
    Foo()
    ImplementsFooer()
}

然后,类型必须实现 ImplementsFooer 方法才能成为 Fooer,清楚地记录事实并在 godoc 的输出中宣布它。

type Bar struct{}
func (b Bar) ImplementsFooer() {}
func (b Bar) Foo() {}

大多数代码都没有使用这些约束,因为它们限制了 界面思想的实用性。但有时,它们是必要的 解决相似接口之间的歧义。

【讨论】:

  • 所以,我可以进行编译时检查并在 NewDog() 中简单地返回一个 *Dog 而不是 Animal 对吧?如果我必须显式声明,那么我将只能调用接口中的方法,而不能调用结构中的方法。
  • @Bharat 完全正确。你可以返回 *Dog 结构体,通过它你可以访问任何以 *Dog 结构体为接收者的方法。
  • 尽管@shal 的回答将这个答案标记为已接受也很有用,因为这个答案有更多解释。
【解决方案3】:

您绝对可以做到,其中一种方法是使用类型断言,如另一个答案here 所示。否则@Himanshu here 的回答很好地描述了这种情况。

我想加入讨论以进一步描述您的方式

可以有一个结构遵循一个接口,也可以定义它自己的方法

MakeDog 方法返回一个 Animal,有很多原因您可以考虑直接返回一个 Dog(或任何具体类型)。

之所以提出这个问题,是因为当我第一次开始用 Go 编程时,有人告诉我有关创建方法的事情:

接受一个接口并返回一个具体类型(例如结构)

接口可以接受任何具体类型。这就是为什么当您不知道要传递给函数的参数类型时使用它们的原因。

我用以下术语进行了谷歌搜索,发现了很多文章

golang 接受接口,返回结构体

例如:https://mycodesmells.com/post/accept-interfaces-return-struct-in-gohttp://idiomaticgo.com/post/best-practice/accept-interfaces-return-structs/

我已经整理了一些演示,扩展了您在问题中的概念,以尝试清楚地描述接口方法以及特定类型的方法和属性

取自this snippet on Playground

package main

import (
    "fmt"
)

type Animal interface {
    MakeNoise() string
}

// printNoise accepts any animal and prints it's noise
func printNoise(a Animal) {
    fmt.Println(a.MakeNoise())
}

type pet struct {
    nLegs int
    color string
}

// describe is available to all types of Pet, but not for an animal
func (p pet) describe() string {
    return fmt.Sprintf(`My colour is "%s", and I have "%d" legs`, p.color, p.nLegs)
}

type Dog struct {
    pet
    favouriteToy string
}

// MakeNoise implements the Animal interface for type Dog
func (Dog) MakeNoise() string {
    return "Bark!"
}

// WagTail is something only a Dog can do
func (d Dog) WagTail() {
    fmt.Println("I am a dog,", d.pet.describe(), ": Wags Tail")
}

type Cat struct {
    pet
    favouriteSleepingPlace string
}

// MakeNoise implements the Animal interface for type Cat
func (Cat) MakeNoise() string {
    return "Meow!"
}

// ArchBack is something only a Cat can do
func (c Cat) ArchBack() {
    fmt.Println("I am a cat,", c.pet.describe(), ": Arches Back")
}

type Bird struct {
    pet
    favoritePerch string
}

// MakeNoise implements the Animal interface for type Cat
func (Bird) MakeNoise() string {
    return "Tweet!"
}

// Hop is something only a Bird can do
func (c Bird) Hop() {
    fmt.Println("I am a bird,", c.pet.describe(), ": Hops to a different perch")
}

func main() {
    dog := Dog{
        pet:          pet{nLegs: 4, color: "Brown"},
        favouriteToy: "Ball",
    }
    printNoise(dog)
    dog.WagTail()

    cat := Cat{
        pet: pet{nLegs: 4, color: "Tabby"},
        favouriteSleepingPlace: "Sofa",
    }
    printNoise(cat)
    cat.ArchBack()

    bird := Bird{
        pet:           pet{nLegs: 2, color: "Rainbow"},
        favoritePerch: "Back of Cage",
    }

    printNoise(bird)
    bird.Hop()

}

【讨论】:

  • type assertion and/or type conversion 太不一样了。请检查类型断言的更多信息。 Go 不像 java 那样工作。
  • 并且 OP 不需要键入断言接口。 OP希望使用相同的结构方法接收器来实现该方法。
  • @Himanshu 我回答的目的是进一步描述 OP could have a struct adhere to an interface and also define it's own methods。我澄清了似乎引起你注意的部分。
【解决方案4】:

是的,您可以运行不属于接口的方法。代码中有两个问题使其无法正常运行。

  1. NewDog 函数返回动物类型。 WagTale 方法附加到 Dog not Animal 上,它给出了错误。返回类型已更改为 Dog。
  2. 此代码中不需要指针,因此已将其删除。

经过这些调整,代码运行正常,所有方法都按预期执行。

Go 游乐场链接:https://play.golang.org/p/LYZJiQND7WW

package main

import (
    "fmt"
)


type Animal interface {
    MakeNoise()
}

type Dog struct {
    color string
}

/* Interface implementation */

func (d Dog) MakeNoise() {
    fmt.Println("Bark!")
}

/* End Interface implementation */

func (d Dog) WagTail() {
    fmt.Println(d.color + " dog: Wag wag")
}

func NewDog(color string) Dog {
    return Dog{color}
}

func main() {
    dog := NewDog("Brown")
    dog.MakeNoise()
    dog.WagTail()
    
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-02-11
    • 1970-01-01
    • 2020-04-06
    • 2020-04-27
    • 1970-01-01
    • 2021-11-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多