05.接口和方法
在 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()
}
输出:
在这个例子中:
- 我们定义了
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()
}
输出:
在这段代码中:
- 定义了
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("停止!")
}
输出:
例子:
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)
}
输出
例子:
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)
}
输出:
这个示例中,我们定义了一个 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)
}
输出:
在代码中,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) // 输出将是排序后的切片
}
输出:
在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)
}
输出:
在这个示例中,我们定义了一个名为 Shape
的接口,它包含 Area
和 Perimeter
方法。
然后,我们定义了 Circle
和 Rectangle
类型,并为它们实现了这些方法。
这样,我们就可以编写一个 PrintShapeInfo
函数,它接受任何实现了 Shape
接口的类型,并打印出形状的面积和周长。
多态
多态是指能够在不同的对象上调用同一个接口方法,并且每个对象根据其具体的类型来响应。
这意味着你可以对不同类型的对象执行相同的操作,而它们会以自己的方式来处理它。
通过理解和使用方法和接口,你可以构建强大的系统,这些系统可以轻松地适应未来的变化,因为它们不依赖于具体的实现。