多态实现的基本要求
- 有一个父类(有接口)
- 有子类实现了父类的全部方法
- 父类类型的变量(指针)指向(引用)子类的具体实现数据变量
案例
案例1:父类实现的是子类的共性
type Animal interface {Yell() //叫声Eat()SizePrint()
}//对于一个接口而言 没有所谓的继承 而是实现了这个接口所有方法的类 就可以使用这个接口// 下面 我创建一个案例 来实现三种动物的叫声 进食和体型
type Dog struct {Color stringSize stringSound stringFood string
}
type Cat struct {Food stringSize stringSound stringLike string
}
type Pig struct {Food stringSize stringSound stringAbility 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 Animalanimal = &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:通过接口实现一个多态方法
// 下面 我们实现一下多态的案例
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
里的两行注释,说明了接口这个可以调用方法,而不能去直接引用这个子类的成员变量。而为了解决这个问题,我们通常使用的方法是构造一个“返回值函数”,案例如下// 下面 我们实现一下多态的案例 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:一个类可以有多个接口
这和函数的原则是很类似的:每个接口(函数)实现的功能很少,这样可以使这个接口(函数)的使用率提高和适用场景增多。
//接下来 我想说明一点 就是一个类可以属于多个接口 我们拿智能手机来举例子
// 在智能手机中 我们可以实现“电话的功能”(接听 拨打 发信息) 也可以实现“相机的功能”(拍照 录像) 还可以实现视频播放器的功能(看电视剧)type Phone interface {//这个接口有智能手机和移动电话两个类Listen()Call()Message()
}type Camera interface {//这个接口有智能手机和相机两个类TakePhoto()RecordVideo()
}type VideoPlayer interface {//这个接口有智能手机和电视两个类PlayVideo()
}type MobilePhone struct {Sound stringTel stringText stringName string
}
type camera struct {Take stringRecord stringName string
}
type TV struct {Play stringName string
}type Smartphone struct {MobilePhonecameraTVName 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:“智能手机”这个东西)。这个只能说仁者见仁智者见智了,如果是省事减少新类型方法的创建,就直接继承即可,在结构体实例里面略显别扭的写入成员变量;或者可以自己新创建一个方法(需要配套称整个接口),来实现:
// 智能手机类方案 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:空接口作为万能类型使用
// 下面的函数实现的是一个各种类型都可以调用的函数
func Myfunc(arg interface{}) {fmt.Println("我要和你")fmt.Println(arg)
}func test1() {Myfunc("date")Myfunc(17)Myfunc(13.14)
}
运行结果:
我要和你
date
我要和你
17
我要和你
13.14
在实际应用中,我们通常要根据传入数据的不同类型来进行不同操作。go很贴心的给interface{}
配备了“断言”这一功能:
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
类型转换失败