但我不确定接口是什么。我们在结构体中定义方法,如果要在结构体上实现方法,我们还需要再次编写该方法。
这意味着接口似乎只是方法定义,在页面上占用了额外的空间。
是否有任何示例可以解释为什么需要接口?
接口是一个太大的主题,无法在这里给出全面的答案,但有一些要点能使它们的使用更清晰。
接口是一种工具。你是否使用它们取决于你自己,但它们可以使代码更清晰,更短,更易读,并且它们可以提供良好的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())
}
从上面的代码中我们可以看到一些重复:当让两个Cat
和Dog
说话时。我们能不能将它们都作为同一种类型实体来处理,例如动物?事实上并不行。当然,我们可以将它们都作为interface{}
来处理,但是这样做的话,我们就无法调用它们的Say()
方法了,因为interface{}
类型的值并没有定义任何方法。
以上这两种类型有一些相似之处:它们都有一个具有相同签名(参数和返回类型)的Say()
方法。我们可以通过一个接口来捕获这个相似之处:
type Sayer interface {
Say() string
}
该接口仅包含方法的签名,但不包括实现细节。
请注意,在Go语言中,如果一个类型的方法集是接口的超集,则该类型会隐式地实现该接口。没有声明意图的必要。这意味着什么?即使我们之前编写它们时没有定义过此接口,也没有对它们进行任何标记,我们先前编写的Cat
和Dog
类型已经实现了这个Sayer
接口。它们就是这样做的。
接口指定行为。实现接口的类型意味着该类型具有接口“指定”的所有方法。
由于两者都实现了Sayer
,因此我们可以将它们都作为Sayer
值进行处理,它们有共同点。看看我们如何统一处理它们:
animals := []Sayer{c, d}
for _, a := range animals {
fmt.Println(reflect.TypeOf(a).Name(), "says:", a.Say())
}
(那个反映部分仅用于获取类型名称,目前不要过多关注它。)
重要的部分是我们可以将Cat
和Dog
视为相同的种类(一个接口类型),并处理/使用它们。如果您想快速创建具有Say()
方法的其他类型,则可以将它们排列在Cat
和Dog
的旁边:
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{}
参数的函数,并使用类型断言或类型开关,这将减少辅助函数的数量,但仍然看起来非常丑陋。
解决方案?是的,使用接口。只需声明函数接受一个接口类型的值,该类型定义了您想要执行的行为,就这样:
func MakeTalk(s Sayer) {
fmt.Println(reflect.TypeOf(s).Name(), "says:", s.Say())
}
你可以使用值为Cat
,Dog
,Horse
或任何其他类型(直到现在还不知道的)具有Say()
方法的对象进行函数调用。很酷。
在Go Playground上尝试这些示例。Say()
,比如这样。 - icza接口提供了一些泛型功能。可以思考一下鸭子类型。
type Reader interface {
Read()
}
func callRead(r Reader) {
r.Read()
}
type A struct {
}
func (_ A) Read() {
}
type B struct {
}
func (_ B) Read() {
}
因为A
和B
都实现了Reader
接口,所以将它们传递给callRead
是可以的。
但如果没有接口,我们就需要为A
和B
编写两个函数。
func callRead(a A){
a.Read()
}
func callRead2(b B){
b.Read()
}
func callRead(r Reader)
。这非常强大! - Evan Morantype Screen interface {
Dimensions() (w uint32, h uint32)
Origin() (x uint32, y uint32)
Clear()
Refresh()
Draw(color Color, point Point)
}
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
sdlGraphics, err := graphics.CreateSDLGraphics(0, 0, 800, 600)
但现在您可以根据此创建一个屏幕类型变量;
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)中存在的烦恼。
我认为 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)
}
我将在这里展示两个有趣的使用案例,涉及到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
并行写入到file
和os.Stdout
中,只需要使用io.Copy(w, r)
。然后使用bytes.NewReader(slice)
从切片中读取,并并行写入到file
和os.Stdout
中。接着,将切片复制到缓冲区中,使用io.Copy(buf, bytes.NewReader(slice))
,然后使用file.Seek(0, 0)
返回文件原点。然后先使用strings.NewReader
从字符串中读取,然后继续使用io.MultiReader(r, file)
和bufio.NewScanner
从file
中读取所有内容,最后使用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语言中的类型断言
还可以看看这个:Go语言:interface{}的含义是什么?
如果您需要实现一个方法,而不考虑结构体。
您可以使用处理程序方法访问本地结构体,并在了解结构体之前使用处理程序。
如果您需要一种与其他或当前结构体不同的行为。
您可能希望您的接口只包含少量方法,因为用户可能永远不会使用它们。 您可能希望根据用例将您的结构体分成不同的部分。
如果您需要实现任何类型。
您可能知道或不知道该类型,但至少有该值。
在Go语言中,接口是一种自定义类型,其他类型(接口)可以实现它。这是Go中实现抽象的主要机制之一。
接口通过描述其行为来描述类型。这是通过描述方法来展示它可以做什么来完成的:
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}]
}
正如您所看到的,这个接口帮助我们将斑马、小丑鱼和逆戟鲸类型添加到 []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]
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!")
}
*T
,但尽量将接口应用于实际实例T
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: &{}
}
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: {}
}