Go 语言中,接口方法是实现抽象、封装和多态的关键构建块。
它们让我们能够定义和使用具有不同实现的通用行为。🧩🌟

方法

方法是一种特殊类型的函数,它是定义在类型(如结构体)上的。
方法的定义和普通函数类似,但它在函数名前有一个额外的参数,这个参数是其接收者(receiver)。
这里是如何为结构体定义一个方法的例子:

type Rectangle struct {
    Width, Height float64
}

// Rectangle 类型的方法
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// 创建 Rectangle 的实例
rect := Rectangle{Width: 10, Height: 5}
// 调用 Area 方法
fmt.Println("矩形的面积是:", rect.Area()) // 输出矩形的面积是: 50

在这个例子中,Area 方法有一个接收者 r,它是 Rectangle 类型的一个实例。

例子:

package main

import "fmt"

// Employee 结构体定义
type Employee struct {
    Name     string
    ID       int
    Position string
}

// Display 方法用于打印 Employee 结构体实例的信息
// `e *Employee` 是接收器参数,表示该方法作用于 Employee 类型的变量
func (e *Employee) Display() {
    fmt.Printf("员工姓名:%s, 员工ID:%d, 职位:%s\n", e.Name, e.ID, e.Position)
}

func main() {
    // 初始化一个 Employee 实例
    emp := Employee{Name: "Alice", ID: 1001, Position: "Engineer"}

    // 调用 emp 实例的 Display 方法打印信息
    emp.Display()
}

输出:

image.png

在这个例子中:

  • 我们定义了 Employee 结构体。
  • 然后,我们为 Employee 结构体定义了一个名为 Display 的方法。注意,Display 方法有一个接收器参数 e *Employee,这意味着它是 Employee 类型的一个方法,而且我们是通过指针接收器来定义的,这样可以直接在原始实例上操作数据,而非副本。
  • main 函数中,我们创建了一个 Employee 实例 emp,并调用了它的 Display 方法来打印员工的详细信息。

例子:

package main

import "fmt"

// Employee 结构体,代表员工的一个数据模型
type Employee struct {
    Name     string // 员工的名字
    ID       int    // 员工的ID
    Position string // 员工的职位
}

// Display 方法用于打印 Employee 实例的详细信息
// 它是 Employee 类型的接收器,意味着它绑定到 Employee 类型上
func (e *Employee) Display() {
    fmt.Printf("员工姓名:%s, 员工ID:%d, 职位:%s\n", e.Name, e.ID, e.Position)
}

// UpdatePosition 方法用于更新 Employee 实例的职位
// 它接受一个 string 类型的 newPosition 参数
func (e *Employee) UpdatePosition(newPosition string) {
    // 更新实例的 Position 字段
    e.Position = newPosition
}

func main() {
    // 创建一个 Employee 实例
    emp := Employee{Name: "Alice", ID: 1001, Position: "Engineer"}

    // 打印初始状态的员工信息
    fmt.Println("更新前的员工信息:")
    emp.Display()

    // 更新员工的职位
    // 调用 UpdatePosition 方法并传入新的职位 "Senior Engineer"
    emp.UpdatePosition("Senior Engineer")

    // 打印更新后的员工信息
    fmt.Println("更新后的员工信息:")
    emp.Display()
}

输出:

image.png

在这段代码中:

  • 定义了 Employee 结构体,它包含三个字段:NameIDPosition
  • Employee 结构体定义了两个方法:DisplayUpdatePositionDisplay 用于打印员工信息,而 UpdatePosition 接受一个字符串参数来更新员工的职位。
  • main 函数中,首先创建了一个 Employee 实例,并使用 Display 方法打印初始信息。
  • 然后,使用 UpdatePosition 方法更新员工的职位,并再次调用 Display 方法来显示更新后的信息。

接口

接口是一个定义方法集的类型,任何具有这些方法的类型都自动实现了该接口。
接口定义了对象的行为。
例如,我们可以定义一个 Shaper 接口:

type Shaper interface {
    Area() float64
}

// 任何拥有 Area 方法的类型都满足 Shaper 接口

现在我们可以写函数,它们可以接受任何实现了 Shaper 接口的类型:

func DescribeShape(s Shaper) {
    fmt.Println("形状的面积是:", s.Area())
}

// 因为 Rectangle 实现了 Shaper 接口,我们可以将 rect 传递给 DescribeShape
DescribeShape(rect)

例子:

package main

import "fmt"

func main() {
	// interface 接口是 go 中的一个数据类型,和 api 接口之类的是两回事
	// 第四步:初始化结构体
	p := Phone{
		Name: "手机",
	}

	// 第五步:通过接口来调用方法
	var p1 Usber
	p1 = p
	p1.Start()
	p1.Stop()
}

// interface 定义的是一个对象的行为规范,接口名字后面一般为 er,如 Usber
// 第一步:定义接口
type Usber interface {
	// type Usber interface 定义一个接口
	// Start() Stop() 定义必须要实现的两个方法
	Start()
	Stop()
}

// 第二步:定义结构体
type Phone struct {
	Name string
}

// 第三步:Phone 结构体实现了接口定义的两个方法
func (p Phone) Start() {
	fmt.Println("启动!")
}

func (p Phone) Stop() {
	fmt.Println("停止!")
}

输出:

image.png

例子:

package main

import "fmt"

func main() {
	// 空接口不限制数据类型
	show(20)
	show("你好!")

	// 切片实现空接口
	slice := []interface{}{1, 2, 3, 4, "张三"}
	fmt.Println(slice)

	// map 实现空接口
	d := map[string]interface{}{
		"name": "张三",
		"age":  25,
	}
	fmt.Println(d)

	// 断言使用
	var x interface{}
	x = 1
	fmt.Printf("%T,%v \n", x, x)
	// 返回两个值,一个是变量本身值,一个是判断是否是当前类型,此处判断是否是 int 类型,是则返回 ok
	v, ok := x.(int)
	fmt.Println(v, ok)
	v = v + 1
	fmt.Println(v)
}

// 空接口可以传递任意数据类型
func show(a interface{}) {
	fmt.Printf("值为:%v,类型为:%T \n", a, a)
}

输出

image.png

例子:

package main

import "fmt"

// 定义一个接口
type Greeter interface {
    Greet() string
}

// 定义一个结构体
type EnglishSpeaker struct{}

// 实现接口方法
func (es EnglishSpeaker) Greet() string {
    return "Hello!"
}

// 定义另一个结构体
type ChineseSpeaker struct{}

// 实现接口方法
func (cs ChineseSpeaker) Greet() string {
    return "你好!"
}

// 任何满足Greeter接口的类型都可以传递给这个函数
func sayGreeting(g Greeter) {
    fmt.Println(g.Greet())
}

func main() {
    var greeter Greeter

    // EnglishSpeaker实现了Greeter接口
    greeter = EnglishSpeaker{}
    sayGreeting(greeter)

    // ChineseSpeaker也实现了Greeter接口
    greeter = ChineseSpeaker{}
    sayGreeting(greeter)
}

输出:

image.png

这个示例中,我们定义了一个 Greeter 接口和两个结构体 EnglishSpeakerChineseSpeaker,它们都实现了 Greet 方法,因此它们都满足了 Greeter 接口。
sayGreeting 函数接受任何实现了 Greeter 接口的类型。

例子:

package main

import "fmt"

// 定义一个接口
type Speaker interface {
	Speak() string
}

// 定义一个Dog结构体
type Dog struct{}

// 实现Speaker接口的Speak方法
func (d Dog) Speak() string {
	return "Woof!"
}

// 定义一个Cat结构体
type Cat struct{}

// 实现Speaker接口的Speak方法
func (c Cat) Speak() string {
	return "Meow!"
}

// 任何满足Speaker接口的类型都可以传递给这个函数
func saySpeaker(g Speaker) {
	fmt.Println(g.Speak())
}

func main() {
	var speaker Speaker

	// Dog实现了Speaker接口
	speaker = Dog{}
	saySpeaker(speaker)

	// Cat也实现了Speaker接口
	speaker = Cat{}
	saySpeaker(speaker)
}

输出:

image.png

在代码中,DogCat 类型都实现了 Speaker 接口,因为它们实现了 Speak() 方法。
这意味着我们可以编写一个函数,它接受 Speaker 接口作为参数,并且它可以用任何实现了该接口的类型调用。

例子:

// 这是标准库的sort.Interface的定义
type Interface interface {
    // Len是数组中元素的数量
    Len() int
    // Less报告索引i的元素是否应该排在索引j的元素之前
    Less(i, j int) bool
    // Swap交换索引i和j的元素
    Swap(i, j int)
}

// 自定义的结构体类型
type MyData []int

// 实现sort.Interface的Len方法
func (m MyData) Len() int {
    return len(m)
}

// 实现sort.Interface的Less方法
func (m MyData) Less(i, j int) bool {
    return m[i] < m[j] // 定义一个简单的升序排序
}

// 实现sort.Interface的Swap方法
func (m MyData) Swap(i, j int) {
    m[i], m[j] = m[j], m[i]
}

// 使用sort.Sort来对MyData类型的切片进行排序
func main() {
    data := MyData{3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5}
    sort.Sort(data)
    fmt.Println(data) // 输出将是排序后的切片
}

输出:

image.png

在Go中,sort.Interface 是标准库提供的一个接口,它定义了排序操作所需的三个方法。
任何实现了这些方法的类型都可以使用 sort.Sort 函数进行排序。
在这个例子中,MyData 类型是一个 int 切片。我们为这个类型实现了 sort.Interface 中的三个方法,这使得我们可以使用 sort.Sort 函数对 MyData 类型的切片进行排序。
在这里,Less 方法定义了如何比较两个元素来决定它们的排序顺序(在这个例子中是升序)。

通过使用接口,我们可以编写更灵活和可重用的代码。
如果一个新类型也有 Area 方法,它就可以被用在任何接受 Shaper 的地方,无需其他改动。


接口进阶

Go 中,接口可以用来定义一个对象的行为。
这不仅仅限于排序。
任何满足接口中声明的方法签名的类型都认为实现了该接口。
这意味着你可以编写函数和方法,它们可以接收任何实现了特定接口的类型。

例子:

package main

import (
	"fmt"
	"math"
)

// 定义一个几何形状的接口
type Shape interface {
	Area() float64
	Perimeter() float64
}

// 定义一个圆形结构体
type Circle struct {
	Radius float64
}

// 实现Shape接口的Area方法
func (c Circle) Area() float64 {
	return math.Pi * c.Radius * c.Radius
}

// 实现Shape接口的Perimeter方法
func (c Circle) Perimeter() float64 {
	return 2 * math.Pi * c.Radius
}

// 定义一个矩形结构体
type Rectangle struct {
	Width, Height float64
}

// 实现Shape接口的Area方法
func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

// 实现Shape接口的Perimeter方法
func (r Rectangle) Perimeter() float64 {
	return 2 * (r.Width + r.Height)
}

// 接受Shape接口类型参数的函数,它可以使用任何实现了Shape接口的类型
func PrintShapeInfo(s Shape) {
	fmt.Printf("Area: %v\n", s.Area())
	fmt.Printf("Perimeter: %v\n", s.Perimeter())
}

func main() {
	circle := Circle{Radius: 8}
	rectangle := Rectangle{Width: 4, Height: 6}

	// 对于不同的形状,我们可以调用相同的函数来输出信息
	PrintShapeInfo(circle)
	PrintShapeInfo(rectangle)
}

输出:

image.png

在这个示例中,我们定义了一个名为 Shape 的接口,它包含 AreaPerimeter 方法。
然后,我们定义了 CircleRectangle 类型,并为它们实现了这些方法。
这样,我们就可以编写一个 PrintShapeInfo 函数,它接受任何实现了 Shape 接口的类型,并打印出形状的面积和周长。


多态

多态是指能够在不同的对象上调用同一个接口方法,并且每个对象根据其具体的类型来响应。
这意味着你可以对不同类型的对象执行相同的操作,而它们会以自己的方式来处理它。

通过理解和使用方法和接口,你可以构建强大的系统,这些系统可以轻松地适应未来的变化,因为它们不依赖于具体的实现。


文章作者: Runfa Li
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Linux 小白鼠
GO GO 入门 go
觉得文章不错,打赏一点吧,1分也是爱~
打赏
微信 微信
支付宝 支付宝