为什么Golang需要接口?

56lgkhnf  于 2022-12-25  发布在  Go
关注(0)|答案(7)|浏览(165)

在Golang中,我们使用结构体和接收器方法。到目前为止一切都很完美。
但是我不确定接口是什么,我们在结构体中定义方法,如果我们想在一个结构体上实现一个方法,我们还是在另一个结构体下再写一次。
这意味着接口似乎只是方法定义,只占用了页面上多余的空间。
有没有什么例子可以解释为什么我需要一个接口?

vxf3dgd4

vxf3dgd41#

接口是一个很大的主题,在这里不能给予一个全面深入的答案,但是有些东西可以让它们的使用变得清晰。
接口是一个工具,你是否使用它们取决于你,但是它们可以使代码更清晰、更短、更可读,并且它们可以在包之间,或者客户端(用户)和服务器(提供者)之间提供一个很好的API。
是的,您可以创建自己的struct类型,并且可以向其“附加”方法,例如:

type Cat struct{}

func (c Cat) Say() string { return "meow" }

type Dog struct{}

func (d Dog) Say() string { return "woof" }

func main() {
    c := Cat{}
    fmt.Println("Cat says:", c.Say())
    d := Dog{}
    fmt.Println("Dog says:", d.Say())
}

我们已经可以在上面的代码中看到一些重复:当CatDog都有意义的时候。我们可以把它们当作同一种实体来处理吗,比如 animal?不太可能。当然我们可以把它们当作interface{}来处理,但是如果我们这样做,我们就不能调用它们的Say()方法,因为interface{}类型的值没有定义任何方法。
上述两种类型中存在一些 * 相似性 *:两者都有一个方法Say(),有相同的签名(参数和结果类型)。我们可以用一个接口来 * 捕获 * 这个:

type Sayer interface {
    Say() string
}

接口只包含方法的 * 签名 *,而不包含它们的 * 实现 *。
注意,在Go语言中,如果一个类型的方法集是一个接口的超集,那么这个类型就隐式地实现了这个接口。这里没有intent的声明。这意味着什么?我们之前的CatDog类型已经实现了这个Sayer接口,尽管我们之前编写它们的时候这个接口定义还不存在。我们没有碰它们做标记什么的。它们就是这样做的。

  • 接口指定行为 *.实现接口的类型意味着该类型具有接口“规定”的所有方法。

由于两者都实现了Sayer,我们可以将两者都作为Sayer的值来处理,它们有一个共同点。

animals := []Sayer{c, d}
for _, a := range animals {
    fmt.Println(reflect.TypeOf(a).Name(), "says:", a.Say())
}

(That reflect部分只是获取类型名,目前还不太重视它。)
最重要的是我们可以把CatDog当作同一种类型(接口类型)来处理,并使用它们,如果你很快就用Say()方法创建了其他类型,它们可以排在CatDog旁边:

type Horse struct{}

func (h Horse) Say() string { return "neigh" }

animals = append(animals, Horse{})
for _, a := range animals {
    fmt.Println(reflect.TypeOf(a).Name(), "says:", a.Say())
}

假设你想写其他代码来处理这些类型。

func MakeCatTalk(c Cat) {
    fmt.Println("Cat says:", c.Say())
}

是的,上面的函数只适用于Cat。如果你想要类似的东西,你必须为每种类型写它。不用说这有多糟糕。
是的,您可以将其编写为接受参数interface{},并使用类型Assert或类型开关,这将减少helper函数的数量,但看起来仍然非常难看。
解决方案是什么?是的,接口。简单地声明函数接受一个接口类型的值,这个接口类型定义了你想用它做的事情,就这样:

func MakeTalk(s Sayer) {
    fmt.Println(reflect.TypeOf(s).Name(), "says:", s.Say())
}

你可以用CatDogHorse或者其他任何一个未知的类型来调用这个函数,它有一个Say()方法。酷。
Go Playground上尝试这些示例。

gpnt7bae

gpnt7bae2#

接口提供了一些类型的泛型。考虑一下duck类型。

type Reader interface{
     Read()
}

func callRead(r Reader){
      r.Read()
}

type A struct{
}
func(_ A)Read(){
}

type B struct{
}
func(_ B)Read(){
}

可以将结构体AB传递给callRead,因为它们都实现了Reader接口,但如果没有接口,则需要为AB编写两个函数。

func callRead(a A){
     a.Read()
}

func callRead2(b B){
     b.Read()
}
pcww981p

pcww981p3#

如前所述,接口是一种工具,并非所有的包都能从中受益,但对于某些编程任务,接口对于抽象和创建包API是非常有用的,尤其是对于库代码或可能以多种方式实现的代码。
以一个负责在屏幕上绘制一些图元图形的软件包为例,我们可以把屏幕的绝对基本要求看作是能够绘制一个像素、清除屏幕、周期性地刷新屏幕内容,以及获得一些关于屏幕的基本几何信息,例如当前的尺寸。因此,“屏幕”界面可能是这样的:

type Screen interface {
    Dimensions() (w uint32, h uint32)
    Origin() (x uint32, y uint32)
    Clear()
    Refresh()
    Draw(color Color, point Point)
}

现在,我们的程序可能有几个不同的“图形驱动程序”,我们的图形软件包可以使用这些驱动程序来满足屏幕的基本要求。您可能使用一些本机操作系统驱动程序,可能是SDL 2软件包,也可能是其他驱动程序。在您的程序中,您可能需要支持多个选项来绘制图形,因为它取决于操作系统环境等。
因此,您可以定义三个struct,每个struct都包含操作系统/库等中底层屏幕绘制例程所需的资源;

type SDLDriver struct {
    window *sdl.Window
    renderer *sdl.Renderer
}

type NativeDriver struct {
    someDataField *Whatever
}

type AnotherDriver struct {
    someDataField *Whatever
}

然后在代码中实现所有这三个结构体的方法接口,以便这三个结构体中的任何一个都可以满足Screen接口的要求

func (s SDLDriver) Dimensions() (w uint32, h uint32) {
    // implement Dimensions()
}

func (s SDLDriver) Origin() (x uint32, y uint32) {
    // implement Origin()
}

func (s SDLDriver) Clear() {
    // implement Clear()
}

func (s SDLDriver) Refresh() {
    // implement Refresh()
}

func (s SDLDriver) Draw(color Color, point Point) {
    // implement Draw()
}

...

func (s NativeDriver) Dimensions() (w uint32, h uint32) {
    // implement Dimensions()
}

func (s NativeDriver) Origin() (x uint32, y uint32) {
    // implement Origin()
}

func (s NativeDriver) Clear() {
    // implement Clear()
}

func (s NativeDriver) Refresh() {
    // implement Refresh()
}

func (s NativeDriver) Draw(color Color, point Point) {
    // implement Draw()
}

... and so on

现在,你的外部程序真的不应该“关心”你可能使用这些驱动程序中的哪一个,只要它可以通过标准接口清除、绘制和刷新屏幕即可。这就是抽象。你在包级别提供程序其余部分工作所需的绝对最小值。只有图形内部的代码需要知道操作如何工作的所有“细节”。
因此,您可能知道需要为给定的环境创建哪个屏幕驱动程序,这可能是在执行开始时根据检查用户系统上可用的内容来决定的。您决定SDL 2是最佳选项,然后创建一个新的SDLGraphics示例;

sdlGraphics, err := graphics.CreateSDLGraphics(0, 0, 800, 600)

但是现在可以从中创建Screen类型变量;

var screen graphics.Screen = sdlGraphics

现在你有了一个通用的“Screen”类型,叫做“screen”,它实现了(假设你已经编程了)Clear()、Draw()、Refresh()、Origin()和Dimensions()方法。

screen.Clear()
screen.Refresh()

如此等等......这样做的好处是你有一个标准的类型叫做'Screen',你的程序的其余部分,并不关心图形库的内部工作,可以不用考虑就可以使用它。你可以把'Screen'传递给任何函数等等,确信它会工作。
接口是非常有用的,它们真的可以帮助你思考代码的功能而不是结构体中的数据,而且小接口更好!
例如,与其在Screen界面中进行大量的渲染操作,不如设计另一个界面,如下所示;

type Renderer interface {
    Fill(rect Rect, color Color)
    DrawLine(x float64, y float64, color Color)
    ... and so on
}

根据你的编程经验和你以前用过的语言,这肯定需要一些时间来适应。如果你一直是一个严格的python程序员,你会发现Go语言非常不同,但如果你一直在使用Java/C++,你会很快理解Go语言。接口给予你面向对象的特性,而没有其他语言(如Java)中存在的麻烦。

7ivaypg9

7ivaypg94#

我认为interface的用处在于实现私有struct字段。例如,如果您有以下代码:

package main
type Halloween struct {
   Day, Month string
}
func NewHalloween() Halloween {
   return Halloween { Month: "October", Day: "31" }
}
func (o Halloween) UK(Year string) string {
   return o.Day + " " + o.Month + " " + Year
}
func (o Halloween) US(Year string) string {
   return o.Month + " " + o.Day + " " + Year
}
func main() {
   o := NewHalloween()
   s_uk := o.UK("2020")
   s_us := o.US("2020")
   println(s_uk, s_us)
}

那么o就可以访问所有的struct字段,你可能不想这样,在这种情况下,你可以使用如下代码:

type Country interface {
   UK(string) string
   US(string) string
}
func NewHalloween() Country {
   o := Halloween { Month: "October", Day: "31" }
   return Country(o)
}

我们所做的唯一更改是添加interface,然后返回 Package 在interface中的struct。在这种情况下,只有方法可以访问struct字段。

v09wglhw

v09wglhw5#

我将在这里展示Go语言中接口的两个有趣的用例:
1-请看这两个简单的界面:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

使用这两个简单的接口,你可以做这个有趣的魔术:

package main

import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "os"
    "strings"
)

func main() {
    file, err := os.Create("log.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    w := io.MultiWriter(file, os.Stdout)
    r := strings.NewReader("You'll see this string twice!!\n")
    io.Copy(w, r)

    slice := []byte{33, 34, 35, 36, 37, 38, 39, 10, 13}
    io.Copy(w, bytes.NewReader(slice)) // !"#$%&'

    buf := &bytes.Buffer{}
    io.Copy(buf, bytes.NewReader(slice))
    fmt.Println(buf.Bytes()) // [33 34 35 36 37 38 39 10 13]

    _, err = file.Seek(0, 0)
    if err != nil {
        panic(err)
    }

    r = strings.NewReader("Hello\nWorld\nThis\nis\nVery\nnice\nInterfacing.\n")
    rdr := io.MultiReader(r, file)
    scanner := bufio.NewScanner(rdr)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
}

输出:

You'll see this string twice!!
!"#$%&'

[33 34 35 36 37 38 39 10 13]
Hello
World
This
is
Very
nice
Interfacing.
You'll see this string twice!!
!"#$%&'

我希望这段代码足够清楚:
使用strings.NewReader读取字符串,并使用io.MultiWriter(仅使用io.Copy(w, r))同时写入fileos.Stdout。然后使用bytes.NewReader(slice)读取切片,并同时写入fileos.Stdout。然后将切片复制到缓冲区io.Copy(buf, bytes.NewReader(slice)),然后使用file.Seek(0, 0)转到文件源,然后首先使用strings.NewReader从字符串读取,然后使用io.MultiReader(r, file)继续读取filebufio.NewScanner,然后使用fmt.Println(scanner.Text())打印所有这些。
2-这是接口的另一个有趣的用法:

package main

import "fmt"

func main() {
    i := show()
    fmt.Println(i) // 0

    i = show(1, 2, "AB", 'c', 'd', []int{1, 2, 3}, [...]int{1, 2})
    fmt.Println(i) // 7

}
func show(a ...interface{}) (count int) {
    for _, b := range a {
        if v, ok := b.(int); ok {
            fmt.Println("int: ", v)
        }
    }
    return len(a)
}

输出:

0
int:  1
int:  2
7

还有一个很好的例子:Go语言中的解释类型Assert
另请参阅:Go: What's the meaning of interface{}?

xwbd5t1u

xwbd5t1u6#

1.如果您需要实现一个方法而不考虑结构。
你可能有一个句柄方法来访问你的本地结构,并在知道结构之前使用句柄。
1.如果您需要其他或当前结构特有的行为。
你可能希望你的界面用一些方法来查看,因为用户可能从来没有使用过它们。你可能希望你的结构被它的用例所划分。
1.如果你需要一个可以实现任何东西的类型。
你可能知道或者不知道类型,但至少你知道值。

xvw2m8pv

xvw2m8pv7#

接口是什么?

在Go语言中,接口是其他类型(接口)能够实现的自定义类型,这是Go语言中实现抽象的主要机制之一。
接口通过类型的行为来描述类型。它是通过描述方法来完成的,方法显示了它可以做什么:
Run in the Playground

package main

import "fmt"

// "Can Move" because has Move() method
type Animal interface {
    Move()
}

// Zebra is an "Animal" because can Move()
type Zebra struct {
    Iam string
}

func (z Zebra) Move() {
    fmt.Println("Zebra says: \"Animal\" can only move. I can move too, therefore I am an \"Animal\"!\n")
}

// "Can Move" because implements the "Animal" that can move
// "Can Swim" because has Swim() method
type Fish interface {
    Animal
    Swim()
}

// ClownFish is an "Animal" because can Move()
// ClownFish is a "Fish" because can Move() and Swim()
type ClownFish struct {
    Iam string
}

func (cf ClownFish) Move() {
    fmt.Println("ClownFish says: \"Animal\" can only move. I can move too, therefore I am an \"Animal\"!")
}

func (cf ClownFish) Swim() {
    fmt.Println("ClownFish says: \"Fish\" can move and swim. I can move and swim too, therefore I am a \"Fish\"!\n")
}

// "Can Move" because implements the "Fish" interface which implements an "Animal" interface that can move
// "Can Swim" because implements the "Fish" interface which can swim
// "Can Whistle" because has Whistle() method
type Dolphin interface {
    Fish
    Whistle()
}

// Orca is an "Animal" because can Move()
// Orca is a "Fish" because can Move() and Swim()
// Orca is a "Dolphin" because can Move(), Swim() and Whistle()
type Orca struct {
    Iam string
}

func (o Orca) Move() {
    fmt.Println("Orca says: \"Animal\" can only move. I can move too, therefore I am an \"Animal\"!")
}

func (o Orca) Swim() {
    fmt.Println("Orca says: \"Fish\" can move and swim. I can move and swim too, therefore I am a \"Fish\"!")
}

func (o Orca) Whistle() {
    fmt.Println("Orca says: \"Dolphin\" can move, swim and whistle. I can move, swim and whistle too, therefore I am a \"Dolphin\"!\n")
}

func main() {

    var pico Zebra = Zebra{Iam: "Zebra animal"}
    // pico can...
    pico.Move()

    var nemo ClownFish = ClownFish{Iam: "Clown fish"}
    // nemo can...
    nemo.Move()
    nemo.Swim()

    var luna Orca = Orca{Iam: "Orca dolphin"}
    // luna can...
    luna.Move()
    luna.Swim()
    luna.Whistle()

    // let's make slices with our "custom" types
    var anything []interface{}
    var animals []Animal
    var fishes []Fish
    var dolphins []Dolphin

    // we can add any type in "empty interface" type slice
    anything = append(anything, pico)
    anything = append(anything, nemo)
    anything = append(anything, luna)
    anything = append(anything, 5)
    anything = append(anything, "abcd")
    fmt.Printf("anything: %v\n", anything) // anything: [{Zebra animal} {Clown fish} {Orca dolphin} 5 abcd]

    // only Animal type can go here
    animals = append(animals, pico)
    animals = append(animals, nemo)
    animals = append(animals, luna)
    fmt.Printf("animals: %v\n", animals) // animals: [{Zebra animal} {Clown fish} {Orca dolphin}]

    // only Fish type can go here
    fishes = append(fishes, nemo)
    fishes = append(fishes, luna)
    fmt.Printf("fishes: %v\n", fishes) // fishes: [{Clown fish} {Orca dolphin}]

    // only Dolphin type can go here
    dolphins = append(dolphins, luna)
    // if you try to add a "Fish" to the slice of "Dolphin"s you will get an error:
    // cannot use nemo (variable of type ClownFish) as type Dolphin in argument to append: ClownFish does not implement Dolphin (missing Whistle method)
    // dolphins = append(dolphins, nemo)
    fmt.Printf("dolphins: %v\n", dolphins) // dolphins: [{Orca dolphin}]
}

正如您所看到的,接口帮助我们将Zebra、ClownFish和Orca类型添加到[]Animal切片中。如果没有接口,这是不可能的。简单地说,接口是一个自定义类型或工具,或者您想称之为什么,它根据其他类型的行为对它们进行分组。

接口{}

空接口interface{}不指定任何方法或实现任何其他接口,这使它成为一个通用类型,因为所有其他类型都符合它。
正如您在上面的代码片段中所看到的,我们可以在anything切片中推送任何类型:

// we can add any type in "empty interface" type slice
anything = append(anything, pico)
anything = append(anything, nemo)
anything = append(anything, luna)
anything = append(anything, 5)
anything = append(anything, "abcd")
fmt.Printf("anything: %v\n", anything) // anything: [{Zebra animal} {Clown fish} {Orca dolphin} 5 abcd]

错误

1.向接口中添加方法,使其变得庞大或特定于上下文
Run in the Playground

package main

    import "fmt"

    // isFish() method makes it context-specific
    // when we start using other animal types are we going to use isBird(), isCarnivore(), etc.?
    type Animal interface {
        Move()
        // isFish()
    }

    // Having a new interface or type which implements "Animal" is a
    // way better design because we can create other types as we need them
    type Fish interface {
        Animal
        FishSpecificBehavior()
    }

    type Salmon struct {
        name string
    }

    func (s Salmon) Move()                 {}
    func (s Salmon) FishSpecificBehavior() {}

    func main() {
        var f Fish = Salmon{"Salty"}
        fmt.Printf("fish is a Salmon by the name \"Salty\": %v\n", f) // fish is a salmon by the name "Salty": {Salty}
        fmt.Println("This is an extendable design!")
    }

1.在接口方法上使用指针接收器*T,但尝试在实际示例T上应用该接口
Run in the Playground

package main

    import "fmt"

    type I interface {
        a()
    }

    type T struct{}

    // in this case *T implements the interface "I", but not T
    func (t *T) a() {}

    type M struct{}

    // in this case M and *M both implement the interface "I"
    func (t M) a() {}

    func main() {
        var b I = &T{}
        // var b I = T{} // cannot use T{} (value of type T) as type I in variable declaration: T does not implement I (a method has pointer receiver)
        fmt.Printf("b: %v\n", b) // b: &{}

        var c1 I = &M{}
        var c2 I = M{}
        fmt.Printf("c1: %v c2: %v\n", c1, c2) // c1: &{} c2: &{}
    }

1.构建深度嵌套的分层接口,而不是使用组合。
Run in the Playground

package main

    import "fmt"

    // This is a bad design when we mimic inheritance and build a deeply nested structure
    type Animal interface {
        AnimalCan()
    }

    type Fish interface {
        Animal
        FishCan()
    }

    type SwordFish interface {
        Animal
        Fish
        SwordFishCan()
    }

    // This is a good design when we use composition and enforce a flatter structure
    type Movable interface {
        Move()
    }

    type Runnable interface {
        Run()
    }

    type Jumpable interface {
        Jump()
    }

    type Agile interface {
        Movable
        Runnable
        Jumpable
    }

    type Ant struct{}

    func (a Ant) Move() {}

    type Tigre struct{}

    func (t Tigre) Move() {}
    func (t Tigre) Run()  {}
    func (t Tigre) Jump() {}

    func main() {
        var ant Movable = Ant{}
        var tigre Agile = Tigre{}

        fmt.Printf("ant: %v tigre: %v", ant, tiger) // ant: {} tigre: {}
    }

相关问题