多态实现的基本要求
- 有一个父类(有接口)
- 有子类实现了父类的全部方法
- 父类类型的变量(指针)指向(引用)子类的具体实现数据变量
案例
案例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()
//通过上面的两个案例,我们可以看出,接口指向一个已有实例或者指向一个创建的实例
}
- 首先,我们注意到,对于一个类的方法而言,我们不需要额外传入这个类的参数,而这个方法直接就可以使用这个类的参数。这点在下面一个案例的多态接口方法中也是如此。
- 再者,我们有可以发现,正如上面所说,一个类想要使用某个接口,就需要实现该接口的所有方法
- 再者,一个接口(父类)可以通过接收变量和直接赋值两种方式接收子类的地址(更清晰的理解是指针指向子类)
输出结果:
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)
}
-
ShowAnimal
这个方法就是一个父类的方法,我们将子类的地址传进去,就可以实现统一个方法被不同类调用的效果 -
值得注意的是
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)
}
-
我们可以发现,只要是父类实现了某个方法,那么子类就可以使用该方法而无需自己实现,但问题就是该方法调用的成员变量只能是子类嵌套里面的(就像在实例里面,我在每个父类中都创建了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
类型转换失败