【多态-类型断言】
面向对象编程-多态 基本介绍
- 变量(实例)具有多种形态。
- 在Go语言,多态特征是通过接口实现的。可以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。
接口体现多态特征的两种形式:
(1)多态参数
如Usb接口案例,Usb usb,即可以接收手机变量,又可以接收相机变量,就体现了Usb 接口多态。
//Usb接口案例,由于·传入实参不同,会判断是哪种类型。
//接口变量就体现出多态的特点。
func (c Computer) Worh来调用Start和Stop方法
usb.Start()
usb.Stop()
}
(2)多态数组
演示一个案例:给 Usb数组 中,存放 Phone 结构体和 Camera结构体变量。
package main
import "fmt"
type Usb interface {
Start()
Stop()
}
type Phone struct {
name string
}
func (p Phone) Start() {
fmt.Println("手机开始工作")
}
func (p Phone) Stop() {
fmt.Println("手机停止工作")
}
type Camera struct {
name string
}
func (c Camera) Start() {
fmt.Println("相机开始工作")
}
func (c Camera) Stop() {
fmt.Println("相机停止工作")
}
func main() {
//定义一个usb接口数组,可以存放Phone和Camera的结构体变量
//这里就体现出多态数组。
var usbArr [3]Usb
usbArr[0] = Phone{"华为"}
usbArr[1] = Phone{"oppo"}
usbArr[2] = Camera{"相机"}
fmt.Println(usbArr)
}
//输出结果:[{华为} {oppo} {相机}]
类型断言
题目: Phone还有一个特有的方法call(),请遍历Usb数组,如果是Phone变量,除了调用Usb 接口声明的方法外,还需要调用Phone 特有方法 call.
解决此类问题,不能直接在接口里面定义方法,需要使用类型断言。所以下面需要了解什么是类型断言。
类型断言:由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言。具体如下:
package main
import "fmt"
type Point struct {
x int
y int
}
func main() {
var a interface{}
var point Point = Point{1,2}
a = point //空接口可以接收任意类型
//如何将接口a赋给一个Point变量?
var b Point
// b = a 此赋值错误,接口不可直接赋值给结构体变量
b = a.(Point) //类型断言
fmt.Println(b)
}
//输出:{1 2}
b = a.(Point) 就是类型断言,表示判断 a 是否指向 Point 类型的变量,如果是就转成 Point 类型并赋给 b 变量,否则报错。
如何在进行断言时,带上检测机制,如果成功就ok,否则也不要报 panic
func main() {
//带检测的类型断言
var x interface{}
var a float32 = 6.6
x = a
//x==>float32 [使用类型断言]
y,flag := x.(float64) //用flag检测类型断言是否失败
if flag {
fmt.Println("convert success")
fmt.Println("y的类型是 %T 值是 %v",y,y)
} else {
fmt.Println("convert fail")
}
fmt.Println("程序结束...")
}
//输出:convert fail
// 程序结束...
类型断言实践1:
Usb接口案例做改进:
给Phone结构体增加一个特有的方法call(),当 Usb 接口接收的是Phone变量时,调用 call() 方法。
package main
import "fmt"
type Usb interface {
Start()
Stop()
}
type Phone struct {
name string
}
func (p Phone) Start() {
fmt.Println("手机开始工作")
}
func (p Phone) Stop() {
fmt.Println("手机停止工作")
}
func(p Phone) Call() {
fmt.Println("手机在打电话...")
}
type Camera struct {
name string
}
func (c Camera) Start() {
fmt.Println("相机开始工作")
}
func (c Camera) Stop() {
fmt.Println("相机停止工作")
}
type Computer struct {
}
func(c Computer) Working(usb Usb) {
usb.Start()
//如果usb是指向Phone结构体变量,则还需要使用Call()方法
if phone,ok := usb.(Phone);ok {
phone.Call() //使用类型断言判断接收的是否为Phone变量
}
usb.Stop()
}
func main() {
var usbArr [3]Usb
usbArr[0] = Phone{"华为"}
usbArr[1] = Phone{"oppo"}
usbArr[2] = Camera{"相机"}
//遍历usbArr
var computer Computer
for _,v := range usbArr{
computer.Working(v)
fmt.Println()
}
}
输出结果:
手机开始工作 //如果是Phone 则调用Call方法输出正在打电话
手机在打电话...
手机停止工作
手机开始工作
手机在打电话...
手机停止工作
相机开始工作
相机停止工作
类型断言实践2:
package main
import "fmt"
//编写一个函数,可以判断输入的参数是什么类型
func TypeJudge(items...interface{}) {
for index,x := range items {
switch x.(type) {
case bool:
fmt.Printf("第%v个参数是 bool 类型,值是%v\n",index+1,x)
case float32:
fmt.Printf("第%v个参数是 float32 类型,值是%v\n",index+1,x)
case float64:
fmt.Printf("第%v个参数是 float64 类型,值是%v\n",index+1,x)
case int,int32,int64:
fmt.Printf("第%v个参数是 整数 类型,值是%v\n",index+1,x)
case string:
fmt.Printf("第%v个参数是 string 类型,值是%v\n",index+1,x)
default:
fmt.Printf("第%v个参数 类型不确定,值是%v\n",index+1,x)
}
}
}
func main() {
var n1 float32 = 6.6
var n2 float64 = 8.8
var n3 int = 99
var name string = "Tom"
address := "北京"
TypeJudge(n1,n2,n3,name,address)
}
输出结果:
第1个参数是 float32 类型,值是6.6
第2个参数是 float64 类型,值是8.8
第3个参数是 整数 类型,值是99
第4个参数是 string 类型,值是Tom
第5个参数是 string 类型,值是北京
【家庭收支软件】
功能1:完成显示主菜单,并且可以退出。
功能2:完成显示收支明细和登记收入的功能。
功能3:完成登记支出的功能。
采用面向过程的方式写代码的缺点:
- 代码都聚集在 main 方法内,main 方法内容多,代码可读性差。
所以可以采取面向对象的方式,把记账软件的功能封装到一个结构体中,然后调用该结构体的方法来实现记账,显示明细。
然后在 main 方法中,创建一个结构体实例,实现记账即可。
//这是一个单独的包用来定义结构体和绑定方法
package utils
import "fmt"
type FamilyAccount struct {
key string //结构体里的字段
loop bool
balance float64
money float64
note string
flag bool
details string
}
//编写一个工厂模式的构造方法,返回一个 *FamilyAccount 实例
func NewFamilyAccount() *FamilyAccount {
return &FamilyAccount{
key:"",
loop:true,
balance:10000.0,
money:0.0,
note:"",
flag:false,
details:"收支\t账户金额\t收支金额\t说明",
}
}
//将显示明细写成一个方法
func (this *FamilyAccount) showDetails(){
fmt.Println("-------------------当前收支明细记录-----------------")
if this.flag {
fmt.Println(this.details)
} else {
fmt.Println("当前没有收支明细...请来一笔吧!")
}
}
//将登记收入写成一个方法
func (this *FamilyAccount) income(){
fmt.Print("本次收入金额:");
fmt.Scanln(&this.money)
this.balance += this.money
fmt.Print("本次收入说明:");
fmt.Scanln(&this.note)
this.details += fmt.Sprintf("\n收入\t %v\t\t %v\t\t%v",this.balance,this.money,this.note)
this.flag = true
}
//将登记支出写成一个方法
func (this *FamilyAccount) pay() {
fmt.Print("本次支出金额:");
fmt.Scanln(&this.money)
//需要判断支出的钱是否合理
if this.money>this.balance {
fmt.Println("余额不足,请重新操作...")
return
}
this.balance -= this.money
fmt.Print("本次支出说明:");
fmt.Scanln(&this.note)
this.details += fmt.Sprintf("\n支出\t %v\t\t %v\t\t%v",this.balance,this.money,this.note)
this.flag = true
}
//将退出写成一个方法
func (this *FamilyAccount) exit() {
fmt.Println("您确定要退出吗? y/n")
choice := ""
for {
fmt.Scanln(&choice)
if choice == "y" ||choice == "n" {
break
}
fmt.Println("你的输入有误,请重新输入 y/n")
}
if choice == "y" {
this.loop = false
}
}
//给结构体绑定相应方法
func (this *FamilyAccount) MainMenu(){
for {
fmt.Println()
fmt.Println("-------------------家庭收支记账软件-----------------")
fmt.Println(" 1.收支明细 ")
fmt.Println(" 2.登记收入 ")
fmt.Println(" 3.登记支出 ")
fmt.Println(" 4.退出软件 ")
fmt.Print(" 请选择(1-4): ")
fmt.Scanln(&this.key)
fmt.Println()
switch this.key{
case "1":
this.showDetails();
case "2":
this.income()
case "3":
this.pay()
case "4":
this.exit()
default:
}
if !this.loop {
break
}
}
fmt.Println("已退出家庭收支记账软件......")
}
//这是引入utils包里的结构体和方法
package main
import(
"fmt"
"go_code/familyaccount/utils"
)
func main() {
// 面对对象方式完成
fmt.Println("面向对象方式完成....")
utils.NewFamilyAccount().MainMenu()
}