欢迎,来自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!
}

这里都发生了什么?

  1. 我们首先定义了一个 Animal 接口,它有一个方法:Sound()
  2. 然后我们创建两种类型,Dog 和 Cat,并赋予它们独特的 Sound() 方法
  3. 在 main() 函数中,我们创建一个变量 myPet,它可以保存满足 Animal 接口的任何内容
  4. 首先,我们分配一只 Dog 给 myPet,让它实现叫,myPet 就汪汪叫
  5. 然后我们分配一只 Cat 给 myPet,再让 myPet 叫,它就喵喵叫

这就是 Go 接口真正神奇的地方:

只要类型具有所需的方法,它就满足接口。不需要显示说明 Dog 实现 Animal 接品。Go 语言足够聪明,可以自行解决!

为什么要关心接口?

起初,我们会想”为什么要费心去做这个?我可以直接编写我的方法”,但随着代码量的增加,接口就变得有用起来。

原因如下:

  1. 灵活性:接口使代码更加灵活。我们可以将一种类型换成另一种,只要它满足接口。就像根据技能来选择用人类似
  2. 多态性:如果不同的类型实现了相同的接口,就可以统一对待它们。这就是接口如此强大的原因——它就像一个可以与任何电视配合使用的通用遥控器
  3. 干净的代码:接口允许编写更干净、更模块化的代码。我们通过定义行为并让类型处理它们自己的实现

多种方法,没问题!

让我们更进一步。假设我们正在构建一个处理形状的系统,并且想要计算不同形状(如圆形和矩形)的面积和周长。这就属于多方法的范畴了。

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 编码愉快!愿你的接口简单,你的结构体永远可行。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注