在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}]
}
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!")
}
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: {}
}
7条答案
按热度按时间vxf3dgd41#
接口是一个很大的主题,在这里不能给予一个全面深入的答案,但是有些东西可以让它们的使用变得清晰。
接口是一个工具,你是否使用它们取决于你,但是它们可以使代码更清晰、更短、更可读,并且它们可以在包之间,或者客户端(用户)和服务器(提供者)之间提供一个很好的API。
是的,您可以创建自己的
struct
类型,并且可以向其“附加”方法,例如:我们已经可以在上面的代码中看到一些重复:当
Cat
和Dog
都有意义的时候。我们可以把它们当作同一种实体来处理吗,比如 animal?不太可能。当然我们可以把它们当作interface{}
来处理,但是如果我们这样做,我们就不能调用它们的Say()
方法,因为interface{}
类型的值没有定义任何方法。上述两种类型中存在一些 * 相似性 *:两者都有一个方法
Say()
,有相同的签名(参数和结果类型)。我们可以用一个接口来 * 捕获 * 这个:接口只包含方法的 * 签名 *,而不包含它们的 * 实现 *。
注意,在Go语言中,如果一个类型的方法集是一个接口的超集,那么这个类型就隐式地实现了这个接口。这里没有intent的声明。这意味着什么?我们之前的
Cat
和Dog
类型已经实现了这个Sayer
接口,尽管我们之前编写它们的时候这个接口定义还不存在。我们没有碰它们做标记什么的。它们就是这样做的。由于两者都实现了
Sayer
,我们可以将两者都作为Sayer
的值来处理,它们有一个共同点。(That reflect部分只是获取类型名,目前还不太重视它。)
最重要的是我们可以把
Cat
和Dog
当作同一种类型(接口类型)来处理,并使用它们,如果你很快就用Say()
方法创建了其他类型,它们可以排在Cat
和Dog
旁边:假设你想写其他代码来处理这些类型。
是的,上面的函数只适用于
Cat
。如果你想要类似的东西,你必须为每种类型写它。不用说这有多糟糕。是的,您可以将其编写为接受参数
interface{}
,并使用类型Assert或类型开关,这将减少helper函数的数量,但看起来仍然非常难看。解决方案是什么?是的,接口。简单地声明函数接受一个接口类型的值,这个接口类型定义了你想用它做的事情,就这样:
你可以用
Cat
,Dog
,Horse
或者其他任何一个未知的类型来调用这个函数,它有一个Say()
方法。酷。在Go Playground上尝试这些示例。
gpnt7bae2#
接口提供了一些类型的泛型。考虑一下duck类型。
可以将结构体
A
和B
传递给callRead
,因为它们都实现了Reader接口,但如果没有接口,则需要为A
和B
编写两个函数。pcww981p3#
如前所述,接口是一种工具,并非所有的包都能从中受益,但对于某些编程任务,接口对于抽象和创建包API是非常有用的,尤其是对于库代码或可能以多种方式实现的代码。
以一个负责在屏幕上绘制一些图元图形的软件包为例,我们可以把屏幕的绝对基本要求看作是能够绘制一个像素、清除屏幕、周期性地刷新屏幕内容,以及获得一些关于屏幕的基本几何信息,例如当前的尺寸。因此,“屏幕”界面可能是这样的:
现在,我们的程序可能有几个不同的“图形驱动程序”,我们的图形软件包可以使用这些驱动程序来满足屏幕的基本要求。您可能使用一些本机操作系统驱动程序,可能是SDL 2软件包,也可能是其他驱动程序。在您的程序中,您可能需要支持多个选项来绘制图形,因为它取决于操作系统环境等。
因此,您可以定义三个struct,每个struct都包含操作系统/库等中底层屏幕绘制例程所需的资源;
然后在代码中实现所有这三个结构体的方法接口,以便这三个结构体中的任何一个都可以满足Screen接口的要求
现在,你的外部程序真的不应该“关心”你可能使用这些驱动程序中的哪一个,只要它可以通过标准接口清除、绘制和刷新屏幕即可。这就是抽象。你在包级别提供程序其余部分工作所需的绝对最小值。只有图形内部的代码需要知道操作如何工作的所有“细节”。
因此,您可能知道需要为给定的环境创建哪个屏幕驱动程序,这可能是在执行开始时根据检查用户系统上可用的内容来决定的。您决定SDL 2是最佳选项,然后创建一个新的SDLGraphics示例;
但是现在可以从中创建Screen类型变量;
现在你有了一个通用的“Screen”类型,叫做“screen”,它实现了(假设你已经编程了)Clear()、Draw()、Refresh()、Origin()和Dimensions()方法。
如此等等......这样做的好处是你有一个标准的类型叫做'Screen',你的程序的其余部分,并不关心图形库的内部工作,可以不用考虑就可以使用它。你可以把'Screen'传递给任何函数等等,确信它会工作。
接口是非常有用的,它们真的可以帮助你思考代码的功能而不是结构体中的数据,而且小接口更好!
例如,与其在Screen界面中进行大量的渲染操作,不如设计另一个界面,如下所示;
根据你的编程经验和你以前用过的语言,这肯定需要一些时间来适应。如果你一直是一个严格的python程序员,你会发现Go语言非常不同,但如果你一直在使用Java/C++,你会很快理解Go语言。接口给予你面向对象的特性,而没有其他语言(如Java)中存在的麻烦。
7ivaypg94#
我认为
interface
的用处在于实现私有struct
字段。例如,如果您有以下代码:那么
o
就可以访问所有的struct
字段,你可能不想这样,在这种情况下,你可以使用如下代码:我们所做的唯一更改是添加
interface
,然后返回 Package 在interface
中的struct
。在这种情况下,只有方法可以访问struct
字段。v09wglhw5#
我将在这里展示Go语言中接口的两个有趣的用例:
1-请看这两个简单的界面:
使用这两个简单的接口,你可以做这个有趣的魔术:
输出:
我希望这段代码足够清楚:
使用
strings.NewReader
读取字符串,并使用io.MultiWriter
(仅使用io.Copy(w, r)
)同时写入file
和os.Stdout
。然后使用bytes.NewReader(slice)
读取切片,并同时写入file
和os.Stdout
。然后将切片复制到缓冲区io.Copy(buf, bytes.NewReader(slice))
,然后使用file.Seek(0, 0)
转到文件源,然后首先使用strings.NewReader
从字符串读取,然后使用io.MultiReader(r, file)
继续读取file
和bufio.NewScanner
,然后使用fmt.Println(scanner.Text())
打印所有这些。2-这是接口的另一个有趣的用法:
输出:
还有一个很好的例子:Go语言中的解释类型Assert
另请参阅:Go: What's the meaning of interface{}?
xwbd5t1u6#
1.如果您需要实现一个方法而不考虑结构。
你可能有一个句柄方法来访问你的本地结构,并在知道结构之前使用句柄。
1.如果您需要其他或当前结构特有的行为。
你可能希望你的界面用一些方法来查看,因为用户可能从来没有使用过它们。你可能希望你的结构被它的用例所划分。
1.如果你需要一个可以实现任何东西的类型。
你可能知道或者不知道类型,但至少你知道值。
xvw2m8pv7#
接口是什么?
在Go语言中,接口是其他类型(接口)能够实现的自定义类型,这是Go语言中实现抽象的主要机制之一。
接口通过类型的行为来描述类型。它是通过描述方法来完成的,方法显示了它可以做什么:
Run in the Playground
正如您所看到的,接口帮助我们将Zebra、ClownFish和Orca类型添加到[]Animal切片中。如果没有接口,这是不可能的。简单地说,接口是一个自定义类型或工具,或者您想称之为什么,它根据其他类型的行为对它们进行分组。
接口{}
空接口
interface{}
不指定任何方法或实现任何其他接口,这使它成为一个通用类型,因为所有其他类型都符合它。正如您在上面的代码片段中所看到的,我们可以在
anything
切片中推送任何类型:错误
1.向接口中添加方法,使其变得庞大或特定于上下文
Run in the Playground
1.在接口方法上使用指针接收器
*T
,但尝试在实际示例T
上应用该接口Run in the Playground
1.构建深度嵌套的分层接口,而不是使用组合。
Run in the Playground