欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

go-面向对象编程(下)

程序员文章站 2022-05-09 15:07:38
面向对象编程思想 抽象 抽象的介绍 我们在前面去定义一个结构体时候,实际上就是把一类事物的共有的 属性( 字段)和 行为( 方法)提取 出来,形成一个 物理模型(结构体)。这种研究问题的方法称为抽象 比如一个银行账户: 面向对象编程三大特性 封装 基本介绍 Golang 仍然有面向对象编程的继承,封 ......

面向对象编程思想-抽象

抽象的介绍

我们在前面去定义一个结构体时候,实际上就是把一类事物的共有的 属性( 字段)和 行为( 方法)提取
出来,形成一个 物理模型(结构体)。这种研究问题的方法称为抽象
比如一个银行账户:

package main

import (
    "fmt"
)
//定义一个结构体account
type account struct {
    accountno string
    pwd string
    balance float64
}

//方法
//1. 存款
func (account *account) deposite(money float64, pwd string)  {

    //看下输入的密码是否正确
    if pwd != account.pwd {
        fmt.println("你输入的密码不正确")
        return 
    }

    //看看存款金额是否正确
    if money <= 0 {
        fmt.println("你输入的金额不正确")
        return 
    }

    account.balance += money
    fmt.println("存款成功~~")

}

//取款
func (account *account) withdraw(money float64, pwd string)  {

    //看下输入的密码是否正确
    if pwd != account.pwd {
        fmt.println("你输入的密码不正确")
        return 
    }

    //看看取款金额是否正确
    if money <= 0  || money > account.balance {
        fmt.println("你输入的金额不正确")
        return 
    }

    account.balance -= money
    fmt.println("取款成功~~")

}

//查询余额
func (account *account) query(pwd string)  {

    //看下输入的密码是否正确
    if pwd != account.pwd {
        fmt.println("你输入的密码不正确")
        return 
    }

    fmt.printf("你的账号为=%v 余额=%v \n", account.accountno, account.balance)

}


func main() {

    //测试一把
    account := account{
        accountno : "gs1111111",
        pwd : "666666",
        balance : 100.0,
    }

    //这里可以做的更加灵活,就是让用户通过控制台来输入命令...
    //菜单....
    account.query("666666")
    account.deposite(200.0, "666666")
    account.query("666666")
    account.withdraw(150.0, "666666")
    account.query("666666")

    


}

面向对象编程三大特性-封装

基本介绍

golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它 oop 语言不一
样,下面我们一一为同学们进行详细的讲解 golang 的三大特性是如何实现的。

封装介绍

封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其
它包只有通过被授权的操作(方法),才能对字段进行操作

封装的理解和好处

1) 隐藏实现细节
2) 提高对 数据进行验证,保证安全合理(age)

如何体现封装

1) 对结构体中的属性进行封装
2) 通过 方法,包 包 实现封装

封装的实现步骤

1) 将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似 private)
2) 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
3) 提供一个首字母大写的 set 方法(类似其它语言的 public),用于对属性判断并赋值
func (var 结构体类型名) setxxx(参数列表) (返回值列表) {
//加入数据验证的业务逻辑
var.字段 = 参数
}
4) 提供一个首字母大写的 get 方法(类似其它语言的 public),用于获取属性的值
func (var 结构体类型名) getxxx() {
return var.age;
}
特别说明:在 golang 开发中并没有特别强调封装,这点并不像 java. 所以提醒学过 java 的朋友,
不用总是用 java 的语法特性来看待 golang, golang 本身对面向对象的特性做了简化的.

看一个案例

请大家看一个程序(person.go),不能随便查看 人的年龄, 工资等隐私,并对输入的年龄进行合理的验
证。设计: model 包(person.go) main 包(main.go 调用 person 结构体)
main.go

package main
import (
    "fmt"
    "go_code/code/chapter11/encapsulate/model"
)

func main() {

    p := model.newperson("smith")
    p.setage(18)
    p.setsal(5000)
    fmt.println(p)
    fmt.println(p.name, " age =", p.getage(), " sal = ", p.getsal())
    
}

moudel

package model
import "fmt"

type person struct {
    name string
    age int   //其它包不能直接访问..
    sal float64
}

//写一个工厂模式的函数,相当于构造函数
func newperson(name string) *person {
    return &person{
        name : name,
    }
}

//为了访问age 和 sal 我们编写一对setxxx的方法和getxxx的方法
func (p *person) setage(age int) {
    if age >0 && age <150 {
        p.age = age
    } else {
        fmt.println("年龄范围不正确..")
        //给程序员给一个默认值
    }
}

func (p *person) getage() int {
    return p.age
}


func (p *person) setsal(sal float64) {
    if sal >= 3000 && sal <= 30000 {
        p.sal = sal
    } else {
        fmt.println("薪水范围不正确..")
        
    }
}

func (p *person) getsal() float64 {
    return p.sal
}

面向对象编程三大特性-继承

看一个问题,引出继承的必要性

一个小问题,看个学生考试系统的程序 extends01.go,提出代码复用的问题
1) pupil 和 graduate 两个结构体的字段和方法几乎,但是我们却写了相同的代码, 代码复用性不

2) 出现代码冗余,而且代码 不利于维护,同时 也不利于功能的扩展。
3) 解决方法-通过 继承方式来解决

package main

import (
    "fmt"
)




//编写一个学生考试系统

type student struct {
    name string
    age int
    score int
}

//将pupil 和 graduate 共有的方法也绑定到 *student
func (stu *student) showinfo() {
    fmt.printf("学生名=%v 年龄=%v 成绩=%v\n", stu.name, stu.age, stu.score)
}
func (stu *student) setscore(score int) {
    //业务判断
    stu.score = score
}

//给 *student 增加一个方法,那么 pupil 和 graduate都可以使用该方法
func (stu *student) getsum(n1 int, n2 int) int {
    return n1 + n2
}

//小学生
type pupil struct { 
    student //嵌入了student匿名结构体
}

//显示他的成绩

//这时pupil结构体特有的方法,保留
func (p *pupil) testing() {
    fmt.println("小学生正在考试中.....")
}

//大学生, 研究生。。


//大学生
type graduate struct {
    student //嵌入了student匿名结构体
}

//显示他的成绩
//这时graduate结构体特有的方法,保留
func (p *graduate) testing() {
    fmt.println("大学生正在考试中.....")
}

//代码冗余.. 高中生....

func main() {

    //当我们对结构体嵌入了匿名结构体使用方法会发生变化
    pupil := &pupil{}
    pupil.student.name = "tom~"
    pupil.student.age = 8
    pupil.testing() 
    pupil.student.setscore(70)
    pupil.student.showinfo()
    fmt.println("res=", pupil.student.getsum(1, 2))


    graduate := &graduate{}
    graduate.student.name = "mary~"
    graduate.student.age = 28
    graduate.testing() 
    graduate.student.setscore(90)
    graduate.student.showinfo()
    fmt.println("res=", graduate.student.getsum(10, 20))
}

继承可以解决代码复用,让我们的编程更加靠近人类思维。
当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体(比如刚才的
student),在该结构体中定义这些相同的属性和方法。
其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个 student 匿名结构体即可
也就是说:在 golang 中,如果一个 struct 嵌套了另一个匿名结构体,那么这个结构体可以直接访
问匿名结构体的字段和方法,从而实现了继承特性。

嵌套匿名结构体的基本语法

type goods struct {
name string
price int
}
type book struct {
goods //这里就是嵌套匿名结构体 goods
writer string
}

继承给编程带来的便利

1) 代码的复用性提高了
2) 代码的扩展性和维护性提高了

继承的深入讨论

1) 结构体可以 使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,
都可以使用。【举例说明】
2) 匿名结构体字段访问可以简化

package main

import (
    "fmt"
)

type a struct {
    name string
    age int
}

func (a *a) sayok() {
    fmt.println("a sayok", a.name)
}

func (a *a) hello() {
    fmt.println("a hello", a.name)
}

type b struct {
    a
    name string 
}

func (b *b) sayok() {
    fmt.println("b sayok", b.name)
}

func main() {

    // var b b
    // b.a.name = "tom"
    // b.a.age = 19
    // b.a.sayok()
    // b.a.hello()

    // //上面的写法可以简化

    // b.name = "smith"
    // b.age = 20
    // b.sayok()
    // b.hello()

    var b b
    b.name = "jack" // ok
    b.a.name = "scott"
    b.age = 100  //ok
    b.sayok()  // b sayok  jack
    b.a.sayok() //  a sayok scott
    b.hello() //  a hello ? "jack" 还是 "scott"

}

对上面的代码小结

(1) 当我们直接通过 b 访问字段或方法时,其执行流程如下比如 b.name

(2) 编译器会先看 b 对应的类型有没有 name, 如果有,则直接调用 b 类型的 name 字段

(3) 如果没有就去看 b 中嵌入的匿名结构体 a 有没有声明 name 字段,如果有就调用,如果没有
继续查找..如果都找不到就报错.
3) 当 结构体和 匿名结构体有相同的字段或者方法时, 编译器采用就近访问原则访问,如希望访问
匿名结构体的字段和方法,可以通过匿名结构体名来区分【举例说明】
4) 结构体嵌入两个(或多个)匿名结构体,如 两个匿名结构体有相同的字段和方法( 同时结构体本身
没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。【举例说明】
5) 如果一个 struct 嵌套了一个有名结构体,这种模式就是 组合,如果是组合关系,那么在访问组合
的结构体的字段或方法时,必须带上结构体的名字
6) 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个 匿名结构体字段的值
说明
1) 如果一个结构体有 int 类型的匿名字段,就不能第二个。
2) 如果需要有多个 int 的字段,则必须给 int 字段指定名字

面向对象编程-多重继承

多重继承说明
如 一个 struct 嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方
法, 从而实现了多重继承。
多重继承细节说明
1) 如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来
区分。【案例演示】
2) 为了保证代码的简洁性,建议大家尽量不使用多重继承

package main
import (
    "fmt"
)

type a struct {
    name string
    age int
}
type b struct {
    name string
    score float64
}
type c struct {
    a
    b
    //name string
}

type d struct {
    a a //有名结构体
}


type goods struct {
    name string
    price float64
}

type brand struct {
    name string
    address string
}

type tv struct {
    goods
    brand   
}

type tv2 struct {
    *goods
    *brand  
}

type monster struct  {
    name string
    age int
}

type e struct {
    monster
    int
    n int
}

func main() {
    var c c
    //如果c 没有name字段,而a 和 b有name, 这时就必须通过指定匿名结构体名字来区分
    //所以 c.name 就会包编译错误, 这个规则对方法也是一样的!
    c.a.name = "tom" // error
    fmt.println("c")

    //如果d 中是一个有名结构体,则访问有名结构体的字段时,就必须带上有名结构体的名字
    //比如 d.a.name 
    var d d 
    d.a.name = "jack"


    //嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
    tv := tv{ goods{"电视机001", 5000.99},  brand{"海尔", "山东"}, }

    //演示访问goods的name
    fmt.println(tv.goods.name)
    fmt.println(tv.price) 

    tv2 := tv{ 
        goods{
            price : 5000.99,
            name : "电视机002", 
        },  
        brand{
            name : "夏普", 
            address :"北京",
        }, 
    }

    fmt.println("tv", tv)
    fmt.println("tv2", tv2)

    tv3 := tv2{ &goods{"电视机003", 7000.99},  &brand{"创维", "河南"}, }

    tv4 := tv2{ 
            &goods{
                name : "电视机004", 
                price : 9000.99,
            },  
            &brand{
                name : "长虹", 
                address : "四川",
            }, 
        }

    fmt.println("tv3", *tv3.goods, *tv3.brand)
    fmt.println("tv4", *tv4.goods, *tv4.brand)


    //演示一下匿名字段时基本数据类型的使用
    var e e
    e.name = "狐狸精"
    e.age = 300
    e.int = 20
    e.n = 40
    fmt.println("e=", e)

}

接口(interface)

基本介绍

按顺序,我们应该学习多态,但是在学习多态前,我们需要讲解接口(interface),因为在 golang 中 多态
特性主要是通过接口来体现的。

接口快速入门

这样的设计需求在 golang 编程中也是会大量存在的,我曾经说过,一个程序就是一个世界,在现实世
界存在的情况,在程序中也会出现。 我们用程序来模拟一下前面的应用场景。
代码实现

package main
import (
    "fmt"
)

//声明/定义一个接口
type usb interface {
    //声明了两个没有实现的方法
    start() 
    stop()
}


//声明/定义一个接口
type usb2 interface {
    //声明了两个没有实现的方法
    start() 
    stop()
    test()
}



type phone struct {

}  

//让phone 实现 usb接口的方法
func (p phone) start() {
    fmt.println("手机开始工作。。。")
}
func (p phone) stop() {
    fmt.println("手机停止工作。。。")
}

type camera struct {

}
//让camera 实现   usb接口的方法
func (c camera) start() {
    fmt.println("相机开始工作~~~。。。")
}
func (c camera) stop() {
    fmt.println("相机停止工作。。。")
}


//计算机
type computer struct {

}

//编写一个方法working 方法,接收一个usb接口类型变量
//只要是实现了 usb接口 (所谓实现usb接口,就是指实现了 usb接口声明所有方法)
func (c computer) working(usb usb) {

    //通过usb接口变量来调用start和stop方法
    usb.start()
    usb.stop()
}

func main() {

    //测试
    //先创建结构体变量
    computer := computer{}
    phone := phone{}
    camera := camera{}

    //关键点
    computer.working(phone)
    computer.working(camera) //
}

接口概念的再说明

interface 类型可以定义一组方法,但是这些不需要实现。并且 interface 不能包含任何变量。到某个
自定义类型(比如结构体 phone)要使用的时候,在根据具体情况把这些方法写出来(实现)。

说明
1) 接口里的 所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的
多态和 高内聚低偶合的思想。
2) golang 中的接口, 不需要 显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个
变量就实现这个接口。因此,golang 中 没有 implement 这样的关键字

注意事项和细节
1) 接口本身 不能创建实例,但是 可以指向一个实现了该接口的自定义类型的变量(实例)
2) 接口中所有的方法都没有方法体,即都是没有实现的方法。
3) 在 golang 中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口。
4) 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
5) 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。
6) 一个自定义类型可以实现多个接口
7) golang 接口中不能有任何变量
8) 一个接口(比如 a 接口)可以继承多个别的接口(比如 b,c 接口),这时如果要实现 a 接口,也必须将 b,c 接口的方法也全部实现。
9) interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil
10) 空接口 interface{} 没有任何方法, 所以所有类型都实现了空接 口, 即我们可以 把任何一个变量
赋给空接口。

package main
import (
    "fmt"
)



type stu struct {
    name string
}

func (stu stu) say() {
    fmt.println("stu say()")
}


type integer int

func (i integer) say() {
    fmt.println("integer say i =" ,i )
}


type ainterface interface {
    say()
}

type binterface interface {
    hello()
}
type monster struct {

}
func (m monster) hello() {
    fmt.println("monster hello()~~")
}

func (m monster) say() {
    fmt.println("monster say()~~")
}

func main() {
    var stu stu //结构体变量,实现了 say() 实现了 ainterface
    var a ainterface = stu
    a.say()


    var i integer = 10
    var b ainterface = i
    b.say() // integer say i = 10


    //monster实现了ainterface 和 binterface
    var monster monster
    var a2 ainterface = monster
    var b2 binterface = monster
    a2.say()
    b2.hello()
}
package main
import (
    "fmt"
)

type binterface interface {
    test01()
}

type cinterface interface {
    test02()
}

type ainterface interface {
    binterface
    cinterface
    test03()
}

//如果需要实现ainterface,就需要将binterface cinterface的方法都实现
type stu struct {
}
func (stu stu) test01() {

}
func (stu stu) test02() {
    
}
func (stu stu) test03() {
    
}

type t  interface{

}

func main() {
    var stu stu
    var a ainterface = stu
    a.test01()

    var t t = stu //ok
    fmt.println(t)
    var t2 interface{}  = stu
    var num1 float64 = 8.8
    t2 = num1
    t = num1
    fmt.println(t2, t)
}
package main
import "fmt"
type usb interface {
    say()
}
type stu struct {
}
func (this *stu) say() {
    fmt.println("say()")
}
func main() {
    var stu stu = stu{}
    // 错误! 会报 stu类型没有实现usb接口 , 
    // 如果希望通过编译,  var u usb = &stu
    var u usb = stu  
    u.say()
    fmt.println("here", u)
}

    

接口编程的最佳实践

实现对 hero 结构体切片的排序: sort.sort(data interface)

package main
import (
    "fmt"
    "sort"
    "math/rand"
)

//1.声明hero结构体
type  hero struct{
    name string
    age int
}

//2.声明一个hero结构体切片类型
type heroslice []hero

//3.实现interface 接口
func (hs heroslice) len() int {
    return len(hs)
}

//less方法就是决定你使用什么标准进行排序
//1. 按hero的年龄从小到大排序!!
func (hs heroslice) less(i, j int) bool {
    return hs[i].age < hs[j].age
    //修改成对name排序
    //return hs[i].name < hs[j].name
}

func (hs heroslice) swap(i, j int) {
    //交换
    // temp := hs[i]
    // hs[i] = hs[j]
    // hs[j] = temp
    //下面的一句话等价于三句话
    hs[i], hs[j] = hs[j], hs[i]
}


//1.声明student结构体
type  student struct{
    name string
    age int
    score float64
}

//将student的切片,安score从大到小排序!!

func main() {

    //先定义一个数组/切片
    var intslice = []int{0, -1, 10, 7, 90}
    //要求对 intslice切片进行排序
    //1. 冒泡排序...
    //2. 也可以使用系统提供的方法 
    sort.ints(intslice) 
    fmt.println(intslice)

    //请大家对结构体切片进行排序
    //1. 冒泡排序...
    //2. 也可以使用系统提供的方法

    //测试看看我们是否可以对结构体切片进行排序
    var heroes heroslice
    for i := 0; i < 10 ; i++ {
        hero := hero{
            name : fmt.sprintf("英雄|%d", rand.intn(100)),
            age : rand.intn(100),
        }
        //将 hero append到 heroes切片
        heroes = append(heroes, hero)
    }

    //看看排序前的顺序
    for _ , v := range heroes {
        fmt.println(v)
    }

    //调用sort.sort
    sort.sort(heroes)
    fmt.println("-----------排序后------------")
    //看看排序后的顺序
    for _ , v := range heroes {
        fmt.println(v)
    }

    i := 10
    j := 20
    i, j = j, i
    fmt.println("i=", i, "j=", j) // i=20 j = 10
}

实现接口 vs 继承

大家可能会对实现接口和继承比较迷茫了, 那么他们究竟有什么区别呢

package main
import (
    "fmt"
)

//monkey结构体
type monkey struct {
    name string
}

//声明接口
type birdable interface {
    flying()
}

type fishable interface {
    swimming()
}

func (this *monkey) climbing() {
    fmt.println(this.name, " 生来会爬树..")
}

//littlemonkey结构体
type littlemonkey struct {
    monkey //继承
}


//让littlemonkey实现birdable
func (this *littlemonkey) flying() {
    fmt.println(this.name, " 通过学习,会飞翔...")
}

//让littlemonkey实现fishable
func (this *littlemonkey) swimming() {
    fmt.println(this.name, " 通过学习,会游泳..")
}

func main() {

    //创建一个littlemonkey 实例
    monkey := littlemonkey{
        monkey {
            name : "悟空",
        },
    }
    monkey.climbing()
    monkey.flying()
    monkey.swimming()

}

1) 当 a 结构体继承了 b 结构体,那么 a 结构就自动的继承了 b 结构体的字段和方法,并且可以直
接使用
2) 当 a 结构体需要扩展功能,同时不希望去破坏继承关系,则可以去实现某个接口即可,因此我们可以认为:实现接口是对继承机制的补充.实现接口可以看作是对 继承的一种补充

接口和继承解决的解决的问题不同
继承的价值主要在于:解决代码的 复用性和 可维护性。
接口的价值主要在于: 设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。
接口比继承更加灵活 person student birdable littlemonkey
接口比继承更加灵活,继承是满足 is - a 的关系,而接口只需满足 like - a 的关系。
接口在一定程度上实现 代码解耦

面向对象编程-多态

基本介绍

变量(实例)具有多种形态。面向对象的第三大特征,在 go 语言,多态特征是通过接口实现的。可以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。
快速入门
在前面的 usb 接口案例,usb usb ,既可以接收手机变量,又可以接收相机变量,就体现了 usb 接口 多态特性。

接口体现多态的两种形式

多态参数

在前面的 usb 接口案例,usb usb ,即可以接收手机变量,又可以接收相机变量,就体现了 usb 接多态。
多态数组
演示一个案例:给 usb 数组中,存放 phone 结构体 和 camera 结构体变量
案例说明:

package main
import (
    "fmt"
)

//声明/定义一个接口
type usb interface {
    //声明了两个没有实现的方法
    start()
    stop()
}

type phone struct {
    name string
}  

//让phone 实现 usb接口的方法
func (p phone) start() {
    fmt.println("手机开始工作。。。")
}
func (p phone) stop() {
    fmt.println("手机停止工作。。。")
}


type camera struct {
    name string
}
//让camera 实现   usb接口的方法
func (c camera) start() {
    fmt.println("相机开始工作。。。")
}
func (c camera) stop() {
    fmt.println("相机停止工作。。。")
}



func main() {
    //定义一个usb接口数组,可以存放phone和camera的结构体变量
    //这里就体现出多态数组
    var usbarr [3]usb
    usbarr[0] = phone{"vivo"}
    usbarr[1] = phone{"小米"}
    usbarr[2] = camera{"尼康"}
    
    fmt.println(usbarr)

}

类型断言

由一个具体的需要,引出了类型断言
基本介绍
类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言,
具体的如下:

package main
import (
    "fmt"
)
type point struct {
    x int
    y int
}
func main() {
    var a interface{}
    var point point = point{1, 2}
    a = point  //ok
    // 如何将 a 赋给一个point变量?
    var b point
    // b = a 不可以
    // b = a.(point) // 可以
    b = a.(point) 
    fmt.println(b) // 


    //类型断言的其它案例
    // var x interface{}
    // var b2 float32 = 1.1
    // x = b2  //空接口,可以接收任意类型
    // // x=>float32 [使用类型断言]
    // y := x.(float32)
    // fmt.printf("y 的类型是 %t 值是=%v", y, y)


    //类型断言(带检测的)
    var x interface{}
    var b2 float32 = 2.1
    x = b2  //空接口,可以接收任意类型
    // x=>float32 [使用类型断言]

    //类型断言(带检测的)
    if y, ok := x.(float32); ok {
        fmt.println("convert success")
        fmt.printf("y 的类型是 %t 值是=%v", y, y)
    } else {
        fmt.println("convert fail")
    }
    fmt.println("继续执行...")

}

对上面代码的说明:
在进行类型断言时,如果类型不匹配,就会报 panic, 因此进行类型断言时,要确保原来的空接口
指向的就是断言的类型.
如何在进行断言时,带上检测机制,如果成功就 ok,否则也不要报 panic

类型断言的最佳实践 1

在前面的 usb 接口案例做改进:
给 phone 结构体增加一个特有的方法 call(), 当 usb 接口接收的是 phone 变量时,还需要调用 call
方法, 走代码:

package main
import (
    "fmt"
)

//声明/定义一个接口
type usb interface {
    //声明了两个没有实现的方法
    start()
    stop()
}

type phone struct {
    name string
}  

//让phone 实现 usb接口的方法
func (p phone) start() {
    fmt.println("手机开始工作。。。")
}
func (p phone) stop() {
    fmt.println("手机停止工作。。。")
}

func (p phone) call() {
    fmt.println("手机 在打电话..")
}


type camera struct {
    name string
}
//让camera 实现   usb接口的方法
func (c camera) start() {
    fmt.println("相机开始工作。。。")
}
func (c camera) stop() {
    fmt.println("相机停止工作。。。")
}

type computer struct {

}

func (computer computer) working(usb usb) {
    usb.start()
    //如果usb是指向phone结构体变量,则还需要调用call方法
    //类型断言..[注意体会!!!]
    if phone, ok := usb.(phone); ok {
        phone.call()
    }
    usb.stop()
}

func main() {
    //定义一个usb接口数组,可以存放phone和camera的结构体变量
    //这里就体现出多态数组
    var usbarr [3]usb
    usbarr[0] = phone{"vivo"}
    usbarr[1] = phone{"小米"}
    usbarr[2] = camera{"尼康"}

    //遍历usbarr
    //phone还有一个特有的方法call(),请遍历usb数组,如果是phone变量,
    //除了调用usb 接口声明的方法外,还需要调用phone 特有方法 call. =》类型断言
    var computer computer
    for _, v := range usbarr{
        computer.working(v)
        fmt.println()
    }
    //fmt.println(usbarr)
}

 类型断言的最佳实践 2

写一函数,循环判断传入参数的类型:

package main
import (
    "fmt"
)


//定义student类型
type student struct {

}

//编写一个函数,可以判断输入的参数是什么类型
func typejudge(items... interface{}) {
    for index, x := range items {
        switch x.(type) {
            case bool :
                fmt.printf("第%v个参数是 bool 类型,值是%v\n", index, x)
            case float32 :
                fmt.printf("第%v个参数是 float32 类型,值是%v\n", index, x)
            case float64 :
                fmt.printf("第%v个参数是 float64 类型,值是%v\n", index, x)
            case int, int32, int64 :
                fmt.printf("第%v个参数是 整数 类型,值是%v\n", index, x)
            case string :
                fmt.printf("第%v个参数是 string 类型,值是%v\n", index, x)
            case student :
                fmt.printf("第%v个参数是 student 类型,值是%v\n", index, x)
            case *student :
                fmt.printf("第%v个参数是 *student 类型,值是%v\n", index, x)
            default :
                fmt.printf("第%v个参数是  类型 不确定,值是%v\n", index, x)
        }
    }
}

func main() {

    var n1 float32 = 1.1
    var n2 float64 = 2.3
    var n3 int32 = 30
    var name string = "tom"
    address := "北京"
    n4 := 300

    stu1 := student{}
    stu2 := &student{}

    typejudge(n1, n2, n3, name, address, n4, stu1, stu2)


}

go的面向对象终于完了(^▽^)!~~~~