欢迎,来自IP地址为:18.97.14.90 的朋友
接口对于 Go 语言小白来说难以理解。接口似乎是个神秘的东西,每个人都在谈论它,但没有人真正以一种合理的方式解释它。”它就像多态,但更简单”,他们说。但每次尝试实现一个接口时,这些代码都会看着我,好像在说:”你到底在做什么,人类?”
但那是过去的事情了。现在,我可以清楚的理解接口了,也希望可以帮助大家避免我早期的困惑。所以,如果你一直在为 Go 接口而伤脑筋,就让我们一步一步地分解它。
那么,接口到底是什么?
让我们从最顶端的概念开始,在 Go 语言中,接口基本上是一种定义行为的方式,但不会注重其工作的细节。想象一下,如果你是一家工厂的老板,你并不关心机器是如何工作的;你只关心它能否生产出产品。这就是 Go 接口的样子:只定义需要发生什么,但不定义应该如何做。
例如,假设我们正在与动物打交道(是的,Go 与动物打交道)。我们知道每种动物都会发出声音,但并不真正关心它是如何发出的。狗汪汪叫,猫喵喵叫,鸭子……嗯,它们嘎嘎叫。于是可以像这样定义一个接口:
type Animal interface { Sound() string }
这是什么?这有点类似于一份合同,上面写着”嘿,任何想要被称为 Animal 的类型都必须有一个 Sound() 方法。”就是这样!没有涉及任何奇怪的魔法。
接下来,让我们举一个非常简单的例子,看看它是如何实际运作的。我们将创造一些动物并让它们会叫(实现 Sound()方法)。
package main import "fmt" // The Animal interface type Animal interface { Sound() string } // Define a Dog type Dog struct{} func (d Dog) Sound() string { return "Woof!" } // Define a Cat type Cat struct{} func (c Cat) Sound() string { return "Meow!" } func main() { // Our Animal variable can hold any type that satisfies the interface var myPet Animal // myPet is now a Dog myPet = Dog{} fmt.Println(myPet.Sound()) // Outputs: Woof! // myPet is now a Cat myPet = Cat{} fmt.Println(myPet.Sound()) // Outputs: Meow! }
这里都发生了什么?
- 我们首先定义了一个 Animal 接口,它有一个方法:Sound()
- 然后我们创建两种类型,Dog 和 Cat,并赋予它们独特的 Sound() 方法
- 在 main() 函数中,我们创建一个变量 myPet,它可以保存满足 Animal 接口的任何内容
- 首先,我们分配一只 Dog 给 myPet,让它实现叫,myPet 就汪汪叫
- 然后我们分配一只 Cat 给 myPet,再让 myPet 叫,它就喵喵叫
这就是 Go 接口真正神奇的地方:
只要类型具有所需的方法,它就满足接口。不需要显示说明 Dog 实现 Animal 接品。Go 语言足够聪明,可以自行解决!
为什么要关心接口?
起初,我们会想”为什么要费心去做这个?我可以直接编写我的方法”,但随着代码量的增加,接口就变得有用起来。
原因如下:
- 灵活性:接口使代码更加灵活。我们可以将一种类型换成另一种,只要它满足接口。就像根据技能来选择用人类似
- 多态性:如果不同的类型实现了相同的接口,就可以统一对待它们。这就是接口如此强大的原因——它就像一个可以与任何电视配合使用的通用遥控器
- 干净的代码:接口允许编写更干净、更模块化的代码。我们通过定义行为并让类型处理它们自己的实现
多种方法,没问题!
让我们更进一步。假设我们正在构建一个处理形状的系统,并且想要计算不同形状(如圆形和矩形)的面积和周长。这就属于多方法的范畴了。
package main import "fmt" // Shape interface with two methods type Shape interface { Area() float64 Perimeter() float64 } // Rectangle struct type Rectangle struct { Width, Height float64 } func (r Rectangle) Area() float64 { return r.Width * r.Height } func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) } // Circle struct type Circle struct { Radius float64 } func (c Circle) Area() float64 { return 3.14 * c.Radius * c.Radius } func (c Circle) Perimeter() float64 { return 2 * 3.14 * c.Radius } func main() { var shape Shape shape = Rectangle{Width: 5, Height: 4} fmt.Println("Rectangle Area:", shape.Area()) // Outputs: 20 fmt.Println("Rectangle Perimeter:", shape.Perimeter()) // Outputs: 18 shape = Circle{Radius: 3} fmt.Println("Circle Area:", shape.Area()) // Outputs: 28.26 fmt.Println("Circle Perimeter:", shape.Perimeter()) // Outputs: 18.84 }
示例代码定义了一个 Shape 接口,需要实现计算面积和周长的方法,之后就可以实现四边形和圆形面积和周长的计算。
一般来讲,接口规定的方法不宜过多,一到两个方法最为合适,这样可以方便的区分不同类型的动作。
空接口(interface{})
这还没有完,让我们更深入地了解一下空接口 interface{}。这是 Go 的说法,”它可以容纳任何类型”。空接口就像一个免费的盒子,可以把任何东西扔进去——字符串、数字、结构等等。
package main import "fmt" func PrintAnything(val interface{}) { fmt.Println(val) } func main() { PrintAnything("Hello, Gophers!") // Outputs: Hello, Gophers! PrintAnything(42) // Outputs: 42 PrintAnything(true) // Outputs: true }
空接口通常用于你事先不知道要处理什么类型(比如 API 或库)的情况。它就像 Go 版的通配符。
拥抱接口
学习 Go 接口一开始感觉就像在迷宫中穿行,但一旦掌握了基础知识,就会打开一个全新的灵活、可重用且干净的代码世界。所以不要害怕深入研究!
从简单开始,实现一些小例子,让 Go 接口的魔法在你身上成长。不久之后,你就会写出干净灵活的代码。
祝 Gophers 编码愉快!愿你的接口简单,你的结构体永远可行。