多态与接口(Golang)

多态实现的基本要求

  1. 有一个父类(有接口)
  2. 有子类实现了父类的全部方法
  3. 父类类型的变量(指针)指向(引用)子类的具体实现数据变量

案例

案例1:父类实现的是子类的共性

go 复制代码
type Animal interface {
	Yell() //叫声
	Eat()
	SizePrint()
}

//对于一个接口而言 没有所谓的继承 而是实现了这个接口所有方法的类 就可以使用这个接口

// 下面 我创建一个案例 来实现三种动物的叫声 进食和体型
type Dog struct {
	Color string
	Size  string
	Sound string
	Food  string
}
type Cat struct {
	Food  string
	Size  string
	Sound string
	Like  string
}
type Pig struct {
	Food    string
	Size    string
	Sound   string
	Ability string
}

// 接下来 我们来实现猫的方法
func (c Cat) Eat() {
	fmt.Println("猫猫爱吃", c.Food)
}
func (c Cat) SizePrint() {
	fmt.Println("猫猫的体型是", c.Size)
}
func (c Cat) Yell() {
	fmt.Println(c.Sound)
}

// 接下来 我们来实现狗的方法
func (d *Dog) Yell() {
	println(d.Sound)
}
func (d *Dog) Eat() {
	println("狗狗吃", d.Food)
}
func (d *Dog) SizePrint() {
	fmt.Println("狗狗的体型是", d.Size)
}

// 接下来 我们来实现猪的方法
func (p *Pig) Yell() {
	println(p.Sound)
}
func (p *Pig) Eat() {
	println("猪猪吃", p.Food)
}
func (p *Pig) SizePrint() {
	fmt.Println("狗狗的体型是", p.Size)
}

func test1() {
	dog1 := Dog{
		Color: "red",
		Size:  "big",
		Sound: "wangwang",
		Food:  "bone",
	} //这里 我们创建的是一个狗的实例 现在我们尝试使用接口类去调用方法

	var animal Animal
	animal = &dog1 //我们可以讲这个借口指针指向dog1这个实例
	animal.Yell()
	animal.Eat()
	animal.SizePrint()

	animal = &Cat{
		Food:  "fish",
		Size:  "small",
		Sound: "miao",
		Like:  "mouse",
	}
	animal.Yell()
	animal.Eat()
	animal.SizePrint()
	//通过上面的两个案例,我们可以看出,接口指向一个已有实例或者指向一个创建的实例

}
  1. 首先,我们注意到,对于一个类的方法而言,我们不需要额外传入这个类的参数,而这个方法直接就可以使用这个类的参数。这点在下面一个案例的多态接口方法中也是如此。
  2. 再者,我们有可以发现,正如上面所说,一个类想要使用某个接口,就需要实现该接口的所有方法
  3. 再者,一个接口(父类)可以通过接收变量和直接赋值两种方式接收子类的地址(更清晰的理解是指针指向子类)

输出结果:

复制代码
wangwang
狗狗吃 bone       
狗狗的体型是 big  
miao              
猫猫爱吃 fish     
猫猫的体型是 small

案例2:通过接口实现一个多态方法

go 复制代码
// 下面 我们实现一下多态的案例
func ShowAnimal(animal Animal) {
	animal.Yell()
	animal.Eat()
	//animal.SizePrint()
	//fmt.Println("输入的动物体型为", animal.Size) 需要注意的是 这个接口不能直接调用输出类的元素 只能调用方法
}

func test2() {
	dog2 := Dog{Size: "small", Food: "bone", Sound: "wangwang"}
	cat2 := Cat{Size: "big", Food: "fish", Sound: "miao", Like: "mouse"}

	//这就实现了一个多态的方法 即同一个方法可以被不同的类调用 (只要这些类属于一个接口)
	ShowAnimal(&dog2)
	ShowAnimal(&cat2)
}
  1. ShowAnimal这个方法就是一个父类的方法,我们将子类的地址传进去,就可以实现统一个方法被不同类调用的效果

  2. 值得注意的是ShowAnimal里的两行注释,说明了接口这个可以调用方法,而不能去直接引用这个子类的成员变量。而为了解决这个问题,我们通常使用的方法是构造一个"返回值函数",案例如下

    go 复制代码
    // 下面 我们实现一下多态的案例
    type Animal2 interface {
    	Yell()
    	Eat()
    	SizePrint()
    	GetSize() string//解决方案
    }
    
    func (d Dog) GetSize() string {
    	return d.Size
    }
    func (c Cat) GetSize() string {
    	return c.Size
    }
    func (p Pig) GetSize() string {
    	return p.Size
    }
    
    func ShowAnimal(animal Animal2) {
    	animal.Yell()
    	animal.Eat()
    	//animal.SizePrint()
    	fmt.Println("输入的动物体型为", animal.GetSize())
    }
    
    func test2() {
    	dog2 := Dog{Size: "small", Food: "bone", Sound: "wangwang"}
    	cat2 := Cat{Size: "big", Food: "fish", Sound: "miao", Like: "mouse"}
    
    	//这就实现了一个多态的方法 即同一个方法可以被不同的类调用 (只要这些类属于一个接口)
    	ShowAnimal(&dog2)
    	ShowAnimal(&cat2)
    }

    运行结果:

    复制代码
    wangwang
    狗狗吃 bone           
    输入的动物体型为 small
    miao                  
    猫猫爱吃 fish         
    输入的动物体型为 big  

运行结果:

复制代码
wangwang
狗狗吃 bone  
miao         
猫猫爱吃 fish

案例3:一个类可以有多个接口

这和函数的原则是很类似的:每个接口(函数)实现的功能很少,这样可以使这个接口(函数)的使用率提高和适用场景增多。

go 复制代码
//接下来 我想说明一点 就是一个类可以属于多个接口 我们拿智能手机来举例子
// 在智能手机中 我们可以实现"电话的功能"(接听 拨打 发信息) 也可以实现"相机的功能"(拍照 录像) 还可以实现视频播放器的功能(看电视剧)

type Phone interface {
	//这个接口有智能手机和移动电话两个类
	Listen()
	Call()
	Message()
}

type Camera interface {
	//这个接口有智能手机和相机两个类
	TakePhoto()
	RecordVideo()
}

type VideoPlayer interface {
	//这个接口有智能手机和电视两个类
	PlayVideo()
}

type MobilePhone struct {
	Sound string
	Tel   string
	Text  string
	Name  string
}
type camera struct {
	Take   string
	Record string
	Name   string
}
type TV struct {
	Play string
	Name string
}

type Smartphone struct {
	MobilePhone
	camera
	TV
	Name string
}

// 下面三个实现的是一个手机类接口的方法
func (m MobilePhone) Listen() {
	println(m.Name, "发出", m.Sound, "的声响")
}
func (m MobilePhone) Call() {
	println(m.Name, m.Tel, "可以拨打电话")
}
func (m MobilePhone) Message() {
	println(m.Name, "可以发送信息,内容为", m.Text)
}

// 下面三个实现的是一个相机类接口的方法
func (c camera) TakePhoto() {
	println(c.Name, "可以", c.Take)
}
func (c camera) RecordVideo() {
	println(c.Name, "可以", c.Record)
}

// 下面三个实现的是一个电视类接口的方法
func (t TV) PlayVideo() {
	println(t.Name, "可以", t.Play)
}

// 下面是三个多态函数
func ShowPhone(phone Phone) {
	phone.Listen()
	phone.Call()
	phone.Message()
} //由于传入参数的限制 这个函数可以被所有手机类调用
func ShowCamera(camera Camera) {
	camera.TakePhoto()
	camera.RecordVideo()
}
func ShowTV(TV VideoPlayer) {
	TV.PlayVideo()
}

func test3() {
	smartphone := Smartphone{
		MobilePhone: MobilePhone{Sound: "beep", Tel: "123456", Text: "hello", Name: "智能手机"},
		camera:      camera{Take: "take", Record: "record", Name: "智能手机"},
		TV:          TV{Play: "play", Name: "智能手机"},
		//Name:        "智能手机",
	} //创建一个智能手机实例

	ShowPhone(&smartphone)
	ShowCamera(&smartphone)
	ShowTV(&smartphone)
}
  1. 我们可以发现,只要是父类实现了某个方法,那么子类就可以使用该方法而无需自己实现,但问题就是该方法调用的成员变量只能是子类嵌套里面的(就像在实例里面,我在每个父类中都创建了Name:"智能手机"这个东西)。这个只能说仁者见仁智者见智了,如果是省事减少新类型方法的创建,就直接继承即可,在结构体实例里面略显别扭的写入成员变量;或者可以自己新创建一个方法(需要配套称整个接口),来实现:

    go 复制代码
    // 智能手机类方案
    type SmartPhone interface {
    	Listen()
    	Call()
    	Message()
    	TakePhoto()
    	RecordVideo()
    	PlayVideo()
    }
    
    func (smartphone Smartphone) Listen() {
    	println(smartphone.Name, "发出", smartphone.Sound, "的声响")
    }
    func (smartphone Smartphone) Call() {
    	println(smartphone.Name, smartphone.Tel, "可以拨打电话")
    }
    
    // 下面是三个多态函数
    func ShowPhone(phone Phone) {
    	phone.Listen()
    	phone.Call()
    	phone.Message()
    } //由于传入参数的限制 这个函数可以被所有手机类调用
    func ShowCamera(camera Camera) {
    	camera.TakePhoto()
    	camera.RecordVideo()
    }
    func ShowTV(TV VideoPlayer) {
    	TV.PlayVideo()
    }
    
    func test3() {
    	smartphone := Smartphone{
    		MobilePhone: MobilePhone{Sound: "beep", Tel: "123456", Text: "hello", Name: "智能手机"},
    		camera:      camera{Take: "take", Record: "record", Name: "智能手机"},
    		TV:          TV{Play: "play", Name: "智能手机"},
    		Name:        "智能手机外部",
    	} //创建一个智能手机实例
    
    	ShowPhone(&smartphone)
    	ShowCamera(&smartphone)
    	ShowTV(&smartphone)
    }

    运行结果:

    复制代码
    智能手机外部 发出 beep 的声响      
    智能手机外部 123456 可以拨打电话       
    智能手机 可以发送信息,内容为 hello
    智能手机 可以 take                 
    智能手机 可以 record               
    智能手机 可以 play   

    也就是说,我们创建的这个智能手机接口,会优先使用自己类的方法,其次再去使用继承别人的方法!crazy

案例4:空接口作为万能类型使用

go 复制代码
// 下面的函数实现的是一个各种类型都可以调用的函数
func Myfunc(arg interface{}) {
	fmt.Println("我要和你")
	fmt.Println(arg)
}

func test1() {
	Myfunc("date")
	Myfunc(17)
	Myfunc(13.14)
}

运行结果:

复制代码
我要和你
date    
我要和你
17      
我要和你
13.14   

在实际应用中,我们通常要根据传入数据的不同类型来进行不同操作。go很贴心的给interface{}配备了"断言"这一功能:

go 复制代码
func MyFunc2(arg interface{}) {
	value, ok := arg.(int) //value用来接收传入类型的值(只有断言成功才能接收到),ok用来接收类型转换的结果是否成功

	if !ok {
		fmt.Println("类型转换失败")
	} else {
		fmt.Println("类型转换成功")
		fmt.Printf("传入类型为:%T\n", value)
	}
}

func test2() {
	MyFunc2("date")
	MyFunc2(17)
	MyFunc2(13.14)
}

运行结果:

复制代码
类型转换失败
类型转换成功   
传入类型为:int
类型转换失败  
相关推荐
mtngt1120 小时前
AI DDD重构实践
go
Grassto2 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto4 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室5 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题5 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo
啊汉7 天前
古文观芷App搜索方案深度解析:打造极致性能的古文搜索引擎
go·软件随想
asaotomo7 天前
一款 AI 驱动的新一代安全运维代理 —— DeepSentry(深哨)
运维·人工智能·安全·ai·go
码界奇点8 天前
基于Gin与GORM的若依后台管理系统设计与实现
论文阅读·go·毕业设计·gin·源代码管理
迷迭香与樱花8 天前
Gin 框架
go·gin
只是懒得想了8 天前
用Go通道实现并发安全队列:从基础到最佳实践
开发语言·数据库·golang·go·并发安全