(8-2)接 口:使用接口

8.2  使用接口

在Go语言中声明并定义接口后,接下来可以使用这个接口。在本节的内容中,将详细讲解使用Go语言接口的知识。

8.2.1  使用接口的基本方法

在Go语言程序中声明接口后,需要实现接口中的每一个成员方法。例如下面是一段定义并使用接口的代码。

// 定义一个接口
type InterfaceName interface {
    Method1() Type     // 方法1,返回值为Type类型
    Method2() Type     // 方法2,返回值为Type类型
    ...
    MethodN() Type     // 方法N,返回值为Type类型
}

// 实现这个接口的结构体类型
type StructName struct {
    // 结构体字段
}

// 实现接口方法1
func (s StructName) Method1() Type {
    // 方法实现
}

// 实现接口方法2
func (s StructName) Method2() Type {
    // 方法实现
}

...

// 实现接口方法N
func (s StructName) MethodN() Type {
    // 方法实现
}

在上述代码中,定义了接口InterfaceName,它包括一组方法Method1、Method2、...、MethodN。然后定义了一个StructName结构体类型,并在该类型上实现了InterfaceName接口中定义的所有方法。

在Go语言中,只要实现了接口中定义的所有方法,即可将任何类型视为该接口的实现。因此,在上面的代码中,类型StructName被视为实现了InterfaceName接口。通过使用接口,可以将不同的类型视为相同的类型,从而更容易地设计模块化和可扩展的软件系统。

实例8-1:餐厅点餐系统(源码路径:Go-codes\8\can.go

在本实例模拟实现了一个餐厅点餐系统,定义了一个Menu接口,以及几个不同的结构体类型来表示不同类型的菜单项。然后,可以使用接口将这些菜单项组合在一起,并对它们进行排序。实例文件can.go的具体实现代码如下所示。

package main

import (
	"fmt"
	"sort"
)

// 定义一个菜单项接口
type MenuItem interface {
	GetName() string
	GetPrice() float64
}

// 定义一个普通菜单项结构体
type RegularMenuItem struct {
	Name  string
	Price float64
}

// 实现MenuItem接口中的GetName方法
func (m RegularMenuItem) GetName() string {
	return m.Name
}

// 实现MenuItem接口中的GetPrice方法
func (m RegularMenuItem) GetPrice() float64 {
	return m.Price
}

// 定义一个特殊菜单项结构体
type SpecialMenuItem struct {
	Name        string
	Price       float64
	Description string
}

// 实现MenuItem接口中的GetName方法
func (m SpecialMenuItem) GetName() string {
	return m.Name
}

// 实现MenuItem接口中的GetPrice方法
func (m SpecialMenuItem) GetPrice() float64 {
	return m.Price
}

// 定义一个菜单结构体
type Menu struct {
	Items []MenuItem
}

// 实现Sort.Interface接口中的Len方法
func (m Menu) Len() int {
	return len(m.Items)
}

// 实现Sort.Interface接口中的Less方法
func (m Menu) Less(i, j int) bool {
	return m.Items[i].GetPrice() < m.Items[j].GetPrice()
}

// 实现Sort.Interface接口中的Swap方法
func (m Menu) Swap(i, j int) {
	m.Items[i], m.Items[j] = m.Items[j], m.Items[i]
}

func main() {
	// 创建一个菜单结构体实例,并添加一些菜单项
	menu := Menu{
		Items: []MenuItem{
			RegularMenuItem{"Burger", 9.99},
			RegularMenuItem{"Fries", 4.99},
			SpecialMenuItem{"Steak", 19.99, "A juicy sirloin steak"},
			SpecialMenuItem{"Lobster", 29.99, "A fresh Maine lobster"},
		},
	}

	// 对菜单项按价格进行排序
	sort.Sort(menu)

	// 打印排序后的菜单项
	for _, item := range menu.Items {
		fmt.Printf("%s - $%.2f\n", item.GetName(), item.GetPrice())
	}
}

在上述代码中定义了接口MenuItem,它包括GetName和GetPrice两个方法。然后,定义了RegularMenuItem和SpecialMenuItem两个不同的结构体类型来表示不同类型的菜单项,并在这些类型上实现了MenuItem接口中定义的方法。接下来,定义了一个Menu结构体来表示菜单,并在该结构体上实现了sort.Interface接口中定义的Len、Less和Swap方法。然后,将不同类型的菜单项组合在一起,并使用sort.Sort函数对其进行排序。执行后会输出:

8.2.2  类型断言(Type Assertion)

类型断言(Type Assertion)是一个使用在接口值上的操作,用于检查接口类型变量所持有的值是否实现了期望的接口或者具体的类型。在Go语言中,实现类型断言的语法格式如下:

value, ok := x.(T)

其中,x 表示一个接口的类型,T 表示一个具体的类型(也可为接口类型)。该断言表达式会返回 x 的值(也就是 value)和一个布尔值(也就是 ok),可根据该布尔值判断 x 是否为 T 类型:

    1. 如果 T 是具体某个类型,类型断言会检查 x 的动态类型是否等于具体类型 T。如果检查成功,类型断言返回的结果是 x 的动态值,其类型是 T。
    2. 如果 T 是接口类型,类型断言会检查 x 的动态类型是否满足 T。如果检查成功,x 的动态值不会被提取,返回值是一个类型为 T 的接口值。
    3. 无论 T 是什么类型,如果 x 是 nil 接口值,类型断言都会失败。

在Go语言中,类型断言和接口之间有非常密切的关系。通过类型断言,可以检查一个值是否实现了某个接口。如果是,那么可以通过将其转换为该接口类型来使用它。

实例8-2:模拟动物园(源码路径:Go-codes\8\dong.go

本实例模拟一个动物园,定义了接口Animal,以及几个不同的结构体类型来表示不同类型的动物。然后,使用类型断言检查每个动物是否能够飞行,并统计出动物园中所有可飞行动物的数量。实例文件dong.go的具体实现代码如下所示。

// 定义一个Animal接口
type Animal interface {
	GetName() string
	CanFly() bool
}

// 定义不同类型的动物结构体
type Bird struct {
	Name string
}

func (b Bird) GetName() string {
	return b.Name
}

func (b Bird) CanFly() bool {
	return true
}

type Snake struct {
	Name string
}

func (s Snake) GetName() string {
	return s.Name
}

func (s Snake) CanFly() bool {
	return false
}

// 模拟一个动物园
func main() {
	// 创建一些动物对象
	bird1 := Bird{"Eagle"}
	bird2 := Bird{"Penguin"}
	snake1 := Snake{"Cobra"}
	snake2 := Snake{"Python"}

	// 将这些动物对象添加到切片中
	animals := []Animal{bird1, bird2, snake1, snake2}

	// 统计动物园中可飞行的动物数量
	count := 0
	for _, animal := range animals {
		if _, ok := animal.(interface{ CanFly() bool }); ok && animal.CanFly() {
			fmt.Printf("%s can fly!\n", animal.GetName())
			count++
		}
	}
	fmt.Printf("There are %d flying animals in the zoo.\n", count)
}

在上述代码中定义了接口Animal,并在Bird和Snake两种结构体类型上实现了该接口。然后,创建了一些不同类型的动物对象,并将它们添加到切片中。接下来,使用类型断言检查每个动物是否能够飞行。通过匿名接口(interface{ CanFly() bool })我们只需要检查是否存在方法CanFly(),就可以判断当前动物是否为可飞行的动物。如果是,则打印出该动物的名称,并将计数器加1。最后,输出动物园中可飞行动物的数量。执行后会输出:

Eagle can fly!
Penguin can fly!
There are 2 flying animals in the zoo.

在路径上,只有Bird类型的Eagle能够飞行,因此打印出"Eagle can fly!"并将计数器加1。最后,我们输出动物园中可飞行动物的数量为1。通过这个实例可以看到,如何使用Go语言的类型断言和接口来实现一个简单而有趣的动物园模拟程序。

8.2.3  类型和接口

在Go语言中,类型和接口之间有着非常紧密的关系。在Go语言中,接口是一种类型,它定义了一组方法的签名。通过实现这些方法,可以将一个类型转换为该接口类型,并使用该接口来操作该类型的对象。具体地说,如果一个类型实现了某个接口中定义的所有方法,则称该类型实现了该接口。我们可以将该类型的对象视为该接口的实现,并且可以通过该接口来调用该类型的方法。这使得我们可以将不同类型的对象统一视为相同的类型进行处理,从而提高代码的灵活性和可扩展性。

请看下面的演示代码(源码路径:Go-codes\8\lei.go),展示了类型和接口之间的关系。

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

// 定义Dog结构体类型,并在该类型上实现Animal接口
type Dog struct {}

func (d Dog) Speak() string {
	return "Woof!"
}

func main() {
	// 创建一个Dog对象,并将其赋值给Animal类型的变量
	var animal Animal = Dog{}

	// 调用Animal接口中的Speak方法,输出"Woof!"
	fmt.Println(animal.Speak())
}

在上述代码中定义了接口Animal,并在结构体类型Dog上实现该接口。然后,创建了一个Dog对象,并将其赋值给Animal类型的变量。最后,调用接口Animal中的方法Speak(),在控制台输出"Woof!"。

通过上述代码可以看出类型和接口之间的关系。具体地说,将Dog对象转换为Animal接口类型,并使用该接口来调用方法Speak()。这使得我们可以将不同类型的对象统一视为相同的类型进行处理,从而提高代码的灵活性和可扩展性。

8.2.4  类型分支

在Go语言中,类型分支是一种用于检查接口对象是否实现了多个接口的机制。它可以让我们根据不同的接口类型来执行不同的代码块,从而提高程序的灵活性和可扩展性。具体地说,类型分支可以使用关键字type…switch来实现。

流程控制语句type…switch的语法被称为是Go语言中最古怪的语法,可以被看作是类型断言的增强版。type…switch和 switch…case 流程控制代码块有些相似。 一个 type…switch 流程控制代码块的语法格式如下所示:

switch t := areaIntf.(type) {
case *Square:
    fmt.Printf("Type Square %T with value %v\n", t, t)
case *Circle:
    fmt.Printf("Type Circle %T with value %v\n", t, t)
case nil:
    fmt.Printf("nil value: nothing to check?\n")
default:
    fmt.Printf("Unexpected type %T\n", t)
}

在上述代码中,变量 t 得到了 areaIntf 的值和类型, 所有 case 语句中列举的类型(nil 除外)都必须实现对应的接口,如果被检测类型没有在 case 语句列举的类型中,就会执行 default 语句。输出结构如下:

Type Square *main.Square with value &{5}

如果跟随在某个 case 关键字后的条目为一个非接口类型(用一个类型名或类型字面表示),则此非接口类型必须实现了断言值 x 的(接口)类型。switch 实现类型分支时的语法格式如下:

switch 接口变量.(type) {
    case 类型1:
        // 变量是类型1时的处理
    case 类型2:
        // 变量是类型2时的处理
    …
    default:
        // 变量不是所有case中列举的类型时的处理
}
  1. 接口变量:表示需要判断的接口类型的变量。
  2. 类型1、类型2……:表示接口变量可能具有的类型列表,满足时,会指定 case 对应的分支进行处理。

实例8-3:使用类型分支检查一个接口对象的类型(源码路径:Go-codes\8\fen.go

在本实例中,使用类型分支检查一个接口对象的类型,并根据结果执行不同的代码块。实例文件fen.go的具体实现代码如下所示。

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

// 定义Dog结构体类型,并实现Animal接口
type Dog struct {}

func (d Dog) Speak() string {
    return "Woof!"
}

// 定义Cat结构体类型,并实现Animal接口
type Cat struct {}

func (c Cat) Speak() string {
    return "Meow!"
}

// 模拟一个动物园
func main() {
    // 创建一个包含不同种类动物的数组
    animals := []Animal{Dog{}, Cat{}}

    // 遍历该数组,并使用类型分支来检查每个动物对象的类型,并输出相应的声音
    for _, animal := range animals {
        switch v := interface{}(animal).(type) {
        case Dog:
            fmt.Println(v.Speak())

        case Cat:
            fmt.Println(v.Speak())
        }
    }
}

对上述代码的具体说明如下:

  1. 定义接口Animal以及Dog和Cat两个结构体类型,并在它们上面分别实现了Speak()方法。然后,创建了一个包含不同种类动物的数组,并使用类型分支来检查每个动物对象的类型,并输出相应的声音。
  2. 在for循环中遍历该数组,并使用类型分支来检查每个动物对象的类型。如果该对象是一只狗,则执行第一个case语句;如果该对象是一只猫,则执行第二个case语句。然后,我们调用该动物对象的Speak方法,并输出相应的声音。

执行后会输出:

Woof!
Meow!

通过这种方式,可以根据不同的接口类型来执行不同的代码块。在上述实例中,我们使用类型分支来模拟一个动物园,并根据不同的动物类型输出相应的声音。

  • 26
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农三叔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值