【Golang】Go语言接口与多态

CSDN 2024-10-12 16:05:02 阅读 93

在这里插入图片描述

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑

🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。

🏆《博客》:Python全栈,Golang开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏:Go语言开发零基础到高阶实战

景天的主页:景天科技苑

在这里插入图片描述

文章目录

接口与多态1. 接口1. 接口的定义2. 接口应用代码示例

2. 模拟多态3. 空接口4. 接口嵌套5. 接口断言

接口与多态

1. 接口

1. 接口的定义

1、Go语言提供了接口数据类型。

2、接口就是把一些共性的方法集合在一起定义。

3、如果有实现类将接口定义的方法全部实现了,那么就代表实现了这个接口

4、隐式实现 Go ,假设A实现了B接口中的所有方法,不需要显示声明

5、接口是方法的定义集合,不需要实现具体的方法内容。名字约束

在Go语言中,接口(Interface)是一个重要的特性,它允许我们定义一组方法但不实现它们,任何类型只要实现了这些方法,就被认为是实现了该接口。

接口体现了程序设计的多态、高内聚、低耦合的思想,是实现面向对象编程中多态性的关键工具。

接口通过interface关键字定义,它是一组方法的集合。接口中的方法没有实现体,即它们没有具体的实现代码。一个类型只要实现了接口中的所有方法,就认为该类型实现了该接口。

如果一个结构体实现了这个接口所有的方法,那这个结构体就是这个接口类型的

2. 接口应用代码示例

接口的基本语法如下:

<code>type 接口名 interface { -- -->

方法名1(参数列表1) 返回值列表1

方法名2(参数列表2) 返回值列表2

...

}

package main

import (

"fmt"

)

// 接口: USB、typec、插座

// 1、Go语言提供了接口数据类型。

// 2、接口就是把一些共性的方法集合在一起定义。

// 3、如果有实现类将接口定义的方法全部实现了,那么就代表实现了这个接口

// 4、隐式实现 Go ,假设A实现了B接口中的所有方法,不需要显示声明

// 5、接口是方法的定义集合,不需要实现具体的方法内容。名字约束

// USB 接口的定义 interface 来定义,方法太多了,要归类,方法的集合

type USB interface { // 接口,方法的集合

input() // 输入方法

output() // 输出方法

}

// Mouse 结构体

type Mouse struct {

name string

}

// 结构体实现了接口的全部方法就代表实现了这个接口,否则不算实现这个接口

func (mouse Mouse) output() {

fmt.Println(mouse.name, "鼠标输出")

}

func (mouse Mouse) input() {

fmt.Println(mouse.name, "鼠标输入")

}

// 接口调用测试

func test(u USB) {

u.input()

u.output()

}

func main() {

// 通过传入接口实现类来进行调用

m1 := Mouse{ name: "罗技"}

// test 参数 USB 类型,如果一个结构体实现了这个接口所有的方法,那这个结构体就是这个接口类型的

test(m1)

//也可以单独测试接口

//m1.input()

k1 := KeyBoard{ name: "雷蛇"}

test(k1)

// 定义高级类型 k1就升级了 KeyBoard --> USB 向上转型

var usb USB

usb = k1

fmt.Println(usb)

// 接口是无法使用实现类的属性的

//fmt.Println(usb.name)

}

// KeyBoard 结构体

type KeyBoard struct {

name string

}

// 结构体实现了接口的全部方法就代表实现了这个接口,否则不算实现这个接口

func (key KeyBoard) output() {

fmt.Println(key.name, "键盘输出")

}

func (key KeyBoard) input() {

fmt.Println(key.name, "键盘输入")

}

在这里插入图片描述

带有参数和返回值的接口

<code>package main

import "fmt"

// Tongxin 定义接口

type Tongxin interface { -- -->

//定义带有参数和返回值的方法

dadianhua(youdian bool) string

jieidanhua(youdian bool) string

}

// People 定义结构体

type People struct {

name string

age int

phone string

}

// 实现接口

func (p People) dadianhua(youdian bool) string {

if youdian {

return fmt.Sprintf("%v 打了电话", p.name)

} else {

return fmt.Sprintf("打电话时手机没电了")

}

}

func (p People) jieidanhua(youdian bool) string {

if youdian {

return fmt.Sprintf("%v 接了电话", p.name)

} else {

return fmt.Sprintf("接电话时手机没电了")

}

}

// 接口测试,有传参,有返回值

func testdianhua(phone Tongxin) {

str1 := phone.dadianhua(false)

str2 := phone.jieidanhua(true)

fmt.Println(str1, str2)

}

func main() {

//创建对象

p := People{ "jingtian", 18, "18898985898"}

//如果一个结构体实现了这个接口所有的方法,那这个结构体就是这个接口类型的

testdianhua(p)

}

在这里插入图片描述

2. 模拟多态

多态是指相同的接口(方法)可以表现出不同的行为。在Go语言中,通过接口实现多态。

在Go语言中,接口定义了一组方法的集合,但不实现它们,而是由具体的类型来实现这些方法。

任何实现了接口中所有方法的类型都被视为该接口的实现。接口是Go语言中实现多态性的关键。

多态:一个事务有多种形态

父类:动物

子类:猫

子类:狗

猫和狗是多态的,他们既可以是自己,也可以是动物,这个就是多态,一个事务有多种形态

Go语言中多态的实现

定义接口

首先,我们需要定义一个接口,该接口包含了一组需要被实现的方法。例如,我们可以定义一个Shape接口,用于计算不同形状的面积。

<code>type Shape interface { -- -->

Area() float64

}

在这个接口中,我们定义了一个Area()方法,该方法返回一个float64类型的值,表示形状的面积。

实现接口

接下来,我们需要定义具体的类型来实现这个接口。这些类型将提供Area()方法的具体实现。

矩形

type Rectangle struct {

Width float64

Height float64

}

func (r Rectangle) Area() float64 {

return r.Width * r.Height

}

圆形

type Circle struct {

Radius float64

}

func (c Circle) Area() float64 {

return math.Pi * c.Radius * c.Radius

}

使用接口进行多态调用

现在,我们可以使用Shape接口来创建不同类型的形状对象,并通过接口进行多态调用。

func main() {

r := Rectangle{ Width: 4, Height: 5}

c := Circle{ Radius: 3}

shapes := []Shape{ r, c}

for _, shape := range shapes {

fmt.Printf("Area: %f\n", shape.Area())

}

}

完整代码

package main

import (

"fmt"

"math"

)

type Shape interface {

Area() float64

}

// Rectangle 矩形

type Rectangle struct {

Width float64

Height float64

}

func (r Rectangle) Area() float64 {

return r.Width * r.Height

}

// Circle 圆形

type Circle struct {

Radius float64

}

func (c Circle) Area() float64 {

return math.Pi * c.Radius * c.Radius

}

func main() {

r := Rectangle{ Width: 4, Height: 5}

c := Circle{ Radius: 3}

shapes := []Shape{ r, c}

for _, shape := range shapes {

fmt.Printf("Area: %f\n", shape.Area())

}

}

在这里插入图片描述

在上面的代码中,我们创建了一个shapes切片,该切片包含了不同类型的形状对象(矩形和圆形)。

然后,我们遍历shapes切片,并通过Shape接口调用Area()方法。由于这两种形状都实现了Shape接口,因此多态性使我们能够以一致的方式调用它们的Area()方法。

多态案例2:

<code>package main

import "fmt"

// Animal3 定义接口

type Animal3 interface { -- -->

eat()

sleep()

}

type Dog3 struct {

name string

}

func (dog Dog3) eat() {

fmt.Println(dog.name, "--eat")

}

func (dog Dog3) sleep() {

fmt.Println(dog.name, "--sleep")

}

// 多态

func main() {

// Dog 两重身份:1、Dog 2、Animal ,多态

dog1 := Dog3{ name: "旺财"}

dog1.eat()

dog1.sleep()

// Dog 也可以是 Animal

test2(dog1)

// 定义一个类型可以为接口类型的变量

// 实际上所有实现类都可以赋值给这个对象

var animal Animal3 // 模糊的 -- 具体化,将具体的实现类赋值给他,才有意义

animal = dog1

//接口是无法使用实现类的属性的

test2(animal)

}

// Animal 接口

func test2(a Animal3) {

a.eat()

a.sleep()

}

在这里插入图片描述

接口的实现类都拥有多态特性:除了自己本身还是他对应接口的类型。

3. 空接口

空接口interface{}不包含任何方法,因此任何类型都实现了空接口。空接口可以被视为能装入任意数量、任意数据类型的数据容器。

因此空接口可以存储任何的类型

空接口不好记,因此在新版本go中起了个名字,叫any

<code>interface{ -- -->} == any

之所以我们的fmt.Println能打印所有东西,就是因为它传入的参数就是any,而any的类型就是空接口

在这里插入图片描述

点击any进去看看,就是空接口

在这里插入图片描述

<code>package main

import "fmt"

// A 定义空接口

type A interface{ -- -->}

// Dogg 所有结构体都实现了空接口A

type Dogg struct {

name string

}

type Catt struct {

name string

}

func testNow(a A) {

fmt.Println(a)

}

// 可以指定定义空接口

// // any is an alias for interface{} and is equivalent to interface{} in all ways.

// type any = interface{}

// 可以传入任何东西

func testNow2(temp interface{ }) {

}

func main() {

//A类型可以是任何类型

var a1 A = Catt{ name: "喵喵"}

var a2 A = Dogg{ name: "旺财"}

var a3 A = 1

var a4 A = "景天科技苑"

fmt.Println(a1)

fmt.Println(a2)

fmt.Println(a3)

fmt.Println(a4)

testNow(a1)

// map结合空接口,就可以存储任何类型数据

map1 := make(map[string]interface{ })

map1["name"] = "dajiang"

map1["age"] = 18

fmt.Println(map1)

// slice,切片定义成空接口类型,也可以存放任何类型数据

s1 := make([]any, 0, 10)

s1 = append(s1, 1, "12312", false, a1, a2)

fmt.Println(s1)

//数组空接口,数组里面的值默认是nil,也可以存放任何数据类型

var arr [4]interface{ }

fmt.Println(arr)

arr[0] = 3

arr[1] = "2"

arr[2] = s1

arr[3] = true

fmt.Println(arr)

}

在这里插入图片描述

4. 接口嵌套

接口可以嵌套其他接口,即一个接口可以继承多个别的接口。这时,如果要实现这个接口,必须实现它继承的所有接口的方法。

<code>package main

import (

"fmt"

)

type AA interface { -- -->

test1()

}

type BB interface {

test2()

}

// CC 接口嵌套 CC : test1()/test2()/test3()

// 如果要实现接口CC,那么需要实现这个三个方法。那这个对象就有3个接口可以转型。

type CC interface {

AA // 导入AA接口中的方法

BB

test3()

}

// Dog7 编写一个结构体实现接口CC

type Dog7 struct {

}

func (dog Dog7) test1() {

fmt.Println("test1")

}

func (dog Dog7) test2() {

fmt.Println("test2")

}

func (dog Dog7) test3() {

fmt.Println("test3")

}

func main() {

// dog 拥有4种形态: Dog7 、CC 、 BB 、 AA

var dog Dog7 = Dog7{ }

dog.test1()

dog.test2()

dog.test3()

// 接口对象只能调用自己接口里面的方法

var a AA = dog

a.test1()

//a.test2() // 向上转型之后只能调用它自己对应的方法

var b BB = dog

b.test2()

//c三个方法都可以调用

var c CC = dog

c.test1()

c.test2()

c.test3()

}

在这里插入图片描述

5. 接口断言

接口断言用于检查接口变量是否持有特定类型的值,并获取该值。被断言的对象必须是接口类型,否则会报错

它有两种形式:不安全断言和类型安全的断言。

不安全断言

instance := 接口对象.(实际类型)

如果不满足类型断言,程序将发生panic报错。

<code>package main

import "fmt"

// 断言 t := i.(T) t:t就是i接口是T类型的 i:接口 T:类型

// 语法:t,ok:= i.(T) ok 隐藏返回值,如果断言成功 ok就是true、否则就是false

func main() { -- -->

//assertsString("11111111111")

assertsString(true) // panic: interface conversion: interface {} is bool, not string

}

// 判断一个变量是不是string类型的

func assertsString(i interface{ }) {

// 如果断言失败,则会抛出 panic 恐慌,程序就会停止执行。

s := i.(string)

fmt.Println(s)

}

在这里插入图片描述

类型安全的断言

instance, ok := 接口对象.(实际类型)

语法:t,ok:= i.(T) ok 隐藏返回值,如果断言成功 ok就是true、否则就是false

如果断言失败,ok将会是false,而instance将会是类型的零值,并且不会触发panic。

接口断言代码示例

<code>package main

import "fmt"

// 断言 t := i.(T) t:t就是i接口是T类型的 i:接口 T:类型

// 语法:t,ok:= i.(T) ok 隐藏返回值,如果断言成功 ok就是true、否则就是false

func main() { -- -->

//assertsString("11111111111")

assertsInt("中国")

}

// 断言失败的情况,我们希望程序不会停止。

func assertsInt(i any) {

r, ok := i.(int)

if ok {

fmt.Println("是我们期望的结果 int")

fmt.Println(r)

} else {

fmt.Println("不是我们期望的结果,无法执行预期操作")

}

}

在这里插入图片描述

多个预期结果判断

通过switch来判断 switch i.(T)

i 必须是接口类型

i.(type)必须在switch中使用

在这里插入图片描述

<code>package main

import "fmt"

// 通过switch来判断 switch i.(T)

type I interface{ -- -->}

// 如果断言的类型同时实现了switch 多个case匹配,默认使用第一个case

// 所以要把范围更小的匹配放前面

func testAssert(i interface{ }) {

// switch i.(type) 接口断言

//i.(type)必须在switch中使用

switch i.(type) {

case string:

fmt.Println("变量为string类型")

case int:

fmt.Println("变量为int类型")

case nil:

fmt.Println("变量为nil类型")

case map[string]int:

fmt.Println("map类型")

case interface{ }:

fmt.Println("变量为interface{}类型")

//空接口与I一样

case I:

fmt.Println("变量为I类型")

// .....

default:

fmt.Println("未知类型")

}

}

func main() {

testAssert("string")

testAssert(1)

var i I // 没有初始化空接口时,默认值为 nil类型 不属于I类型

var i2 I = 1 // 只有赋值了之后,才是对应的类型

testAssert(i)

testAssert(i2)

//map类型

j := make(map[string]int)

testAssert(j)

}

在这里插入图片描述



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。