多态与接口(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
类型转换失败  
相关推荐
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
童先生10 小时前
Go 项目中实现类似 Java Shiro 的权限控制中间件?
开发语言·go
幼儿园老大*11 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
架构师那点事儿16 小时前
golang 用unsafe 无所畏惧,但使用不得到会panic
架构·go·掘金技术征文
于顾而言1 天前
【笔记】Go Coding In Go Way
后端·go
qq_172805591 天前
GIN 反向代理功能
后端·golang·go
follycat2 天前
2024强网杯Proxy
网络·学习·网络安全·go
OT.Ter2 天前
【力扣打卡系列】单调栈
算法·leetcode·职场和发展·go·单调栈
探索云原生2 天前
GPU 环境搭建指南:如何在裸机、Docker、K8s 等环境中使用 GPU
ai·云原生·kubernetes·go·gpu
OT.Ter2 天前
【力扣打卡系列】移动零(双指针)
算法·leetcode·职场和发展·go