在 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 结构体,它包含三个字段:Name、ID 和 Position。
  • 为 Employee 结构体定义了两个方法:Display 和 UpdatePosition。Display 用于打印员工信息,而 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 接口和两个结构体 EnglishSpeaker 与 ChineseSpeaker,它们都实现了 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

在代码中,Dog 和 Cat 类型都实现了 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 的接口,它包含 Area 和 Perimeter 方法。
然后,我们定义了 Circle 和 Rectangle 类型,并为它们实现了这些方法。
这样,我们就可以编写一个 PrintShapeInfo 函数,它接受任何实现了 Shape 接口的类型,并打印出形状的面积和周长。


多态

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

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


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