• 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 
    ", 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、C#,不用总是用 java,C# 的语法特性来看待 Golang, Golang 本身对面向对象的特性做了简化的.
    快速入门案例:
      看一个程序(person.go),不能随便查看人的年龄,工资等隐私,并对输入的年龄进行合理的验证。设计: model 包(person.go) main 包(main.go 调用 Person 结构体)
     
    代码如下:
    model/person.go
    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
    }
    main/main.go
    package main
    
    import (
        "fmt"
        mdoel "go_code/test/models"
    )
    
    func main() {
    
        p := mdoel.NewPerson("bingle")
        p.SetAge(18)
        p.SetSal(1800)
        fmt.Println(p)
        fmt.Println(p.Name, " age =", p.GetAge(), " sal = ", p.GetSal())
    
    }

    面向对象编程三大特性-继承
    继承基本介绍和示意图
    1、继承可以解决代码复用,让我们的编程更加靠近人类思维。
    2、当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法
    3、

    也就是说:在 Golang 中,如果一个 struct 嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。

    嵌套匿名结构体的基本语法
    type Goods struct {
        Name  string
        Price int
    }
    type Book struct {
        Goods  //这里就是嵌套匿名结构体Goods
        Writer string
    }
    快速入门案例

    代码如下:

    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
    ", 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 = "bingle-pupil~"
        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 = "bingle-graduate~"
        graduate.Student.Age = 18
        graduate.testing()
        graduate.Student.SetScore(90)
        graduate.Student.ShowInfo()
        fmt.Println("res=", graduate.Student.GetSum(10, 20))
    }

    继承给编程带来的便利
    1、代码的复用性提高了
    2、代码的扩展性和维护性提高了
    继承的深入讨论
    1、结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用。
    如下代码:
    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 = "bingle"
        b.A.age = 19
        b.A.SayOk()
        b.A.hello()
    }

    2、匿名结构体字段访问可以简化,如下

    func main() {
        var b B
        // b.A.Name = "bingle"
        // b.A.age = 19
        // b.A.SayOk()
        // b.A.hello()
    
        //上面的写法可以简化
    
        b.Name = "bingle"
        b.age = 20
        b.SayOk()
        b.hello()
    }

    对上面的代码小结
      1、当我们直接通过 b 访问字段或方法时,其执行流程如下比如 b.Name

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

      3、如果没有就去看 B 中嵌入的匿名结构体 A有没有声明 Name 字段,如果有就调用,如果没有继续查找..如果都找不到就报错

    3、当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分
    代码如下:
    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)
    }
    
    func (a *A) say() {
        fmt.Println("A say", a.Name)
    }
    
    func (a *A) Hello()  {
        fmt.Println("A Hello", a.Name)
    }
    
    type B struct {
        A
        Name string
        Age  int
    }
    
    func (b *B) SayOk() {
        fmt.Println("B SayOk", b.Name)
    }
    
    func (b *B) say() {
        fmt.Println("B say", b.Name)
    }
    
    func (b *B) Hello()  {
        fmt.Println("B Hello", b.Name)
    }
    
    func main() {
        var b B
        b.Name = "bingle-b"     // 这时就近原则,会访问B结构体的Name字段
        b.A.Name = "bingle-b-a" // b.A.Name 就明确指定访问 A 匿名结构体的Name字段
        b.Age = 18
        b.say() // 这时就近原则,会访问B结构体的say函数
        b.Hello()
        b.A.Hello() // b.A.Hello() 就明确执行访问 A 匿名结构体的Hello函数
    }

    4、结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。

    代码如下:

    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
    }
    
    func main() {
        var c C
        // 如果 c 没有Name字段,而A 和 B 有Name,这时就必须通过指定匿名结构体名字来区分
        // 所以 c.Name 就会抱编译错误,这个规则对方法也是一样的
        c.A.Name="bingle"
        fmt.Println(c)
    }

    5、如果一个 struct 嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字

    type D struct {
        a A // 有名结构体   组合关系
    }
    
    func main() {
        // 如果D 中有一个有名结构体,则访问有名结构体的字段时,就必须要带上有名结构体的名字
        // 比如d.a.Name
        var d D
        d.a.Name="bingle"
    }

    6、嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值

    package main
    
    import (
        "fmt"
    )
    
    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() {
        //嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
        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)
    
    }

    面向对象编程-多重继承
    多重继承说明
      如一个 struct 嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承
    通过一个案例来说明多重继承使用,代码如下:
    type Goods struct {
        Name string
        Price float64
    }
    
    type Brand struct {
        Name string
        Address string
    }
    
    type TV struct {
        Goods
        Brand    
    }
    多重继承细节说明
    1、如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分。
        //嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
        tv := TV{ Goods{"电视机001", 5000.99},  Brand{"海尔", "山东"}, }
    
        //演示访问Goods的Name
        fmt.Println(tv.Goods.Name)
        fmt.Println(tv.Price) 

    2、为了保证代码的简洁性,建议大家尽量不使用多重继承

    接口

    为什么有接口

    USB插槽就是现实中的接口

    你可以把手机、相机、U盘都插在USB插槽上,而不用担心那个插槽是专门插哪个的,原因是,做USB插槽的厂家和生产各种设备的厂家,都遵守了统一的规定,包括尺寸、排线等。

    什么是接口:
      接口可以理解为对一组方法声明进行的统一命名,但是这些方法没有提供任何实现。也就是说,把一组方法声明在一个接口中,然后继承于该接口的类都需要实现这些方法。通过接口,你可以对方法进行统一管理,避免了在每一种类型中重复定义这些方法。
    接口的快速入门
      这样的设计需求在 Golang 编程中也是会大量存在的,一个程序就是一个世界,在现实世界存在的情况,在程序中也会出现。我们用程序来模拟一下前面的应用场景
    代码如下:
    package main
    
    import "fmt"
    
    // 声明/定义一个接口
    type Usb interface {
        // 声明了两个没有实现的方法
        Start()
        Stop()
    }
    
    type Phone struct {
    }
    
    // 让 Phone 实现 Usb 接口的方法
    func (phone Phone) Start() {
        fmt.Println("手机开始工作。。。")
    }
    
    func (phone Phone) Stop() {
        fmt.Println("手机停止工作。。。")
    }
    
    type Camera struct {
    }
    
    // 让 Camera 实现 Usb 接口的方法
    func (camera Camera) Start() {
        fmt.Println("相机开始工作。。。")
    }
    
    func (camera Camera) Stop() {
        fmt.Println("相机停止工作。。。")
    }
    
    // 计算机
    type Computer struct {
    }
    
    // 编写一个方法 Working 方法,接收一个 Usb 接口类型变量
    // 只要是实现了 Usb 接口 (所谓实现 Usb 接口,就是指实现了 Usb 接口声明所有方法)
    // usb 变量会根据传入的实参,来判断到底是 Phone,还是 Camera
    func (computer 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 或者C#中的 : 这样的关键字 

    接口注意事项和细节
    1、接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)。这点不难理解,和Java,C#是一样的概念
    package main
    
    import "fmt"
    
    type AInterface interface {
        Say()
    }
    
    type Student struct {
        Name string
    }
    
    func (student Student) Say() {
        fmt.Println("student Say()...")
    }
    
    func main() {
        var student Student // 结构体变量,实现了Say() 实现了 AInterface
        var a AInterface = student
        a.Say()
    }

    2、接口中所有的方法都没有方法体,即都是没有实现的方法。

    3、在 Golang 中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口。(在C#中,一个类继承接口,需要实现这个接口的所有方法,而且VS编译器会提醒需要实现接口中的方法)

    4、一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型

    5、只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。

    package main
    
    import "fmt"
    
    type AInterface interface {
        Say()
    }
    
    type integer int
    
    func (i integer) Say() {
        fmt.Println("integer Say i is ", i)
    }
    
    func main() {
        var i integer = 20
        var b AInterface = i
        b.Say()
    }

    6、一个自定义类型可以实现多个接口(多实现,单继承)

    package main
    
    import "fmt"
    
    type AInterface interface {
        Say()
    }
    
    type BInterface interface {
        Hello()
    }
    
    type Person struct {
    }
    
    func (p Person) Hello() {
        fmt.Println("Person Hello()~~")
    }
    
    func (p Person) Say() {
        fmt.Println("Person Say()~~")
    }
    
    func main() {
        var person Person
        var a2 AInterface = person
        var b2 BInterface = person
        a2.Say()
        b2.Hello()
    }

    7、Golang 接口中不能有任何变量

    8、一个接口(比如 A 接口)可以继承多个别的接口(比如 B,C 接口),这时如果要实现 A 接口,也必须将 B,C 接口的方法也全部实现。

    9、interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil

    10、空接口 interface{} 没有任何方法,所以所有类型都实现了空接口, 即我们可以把任何一个变量赋给空接口

    接口编程的最佳实践
    package main
    
    import (
        "fmt"
        "sort"
    )
    
    func main() {
        // 先定义一个数组/切片
        var intSlice = []int{0, -1, 10, 7, 90}
        // 要求对 intSlice切片进行排序
        // 1.冒泡排序...
        // 2.也可以使用系统提供的方法
        sort.Ints(intSlice) // 中文文档 http://docscn.studygolang.com/pkg/sort/#Ints
        fmt.Println(intSlice)
    }

    这个地方,使用冒泡排序或者系统提供的排序方法,实现了排序。

    实现对 Hero 结构体切片的排序: sort.Sort(data Interface)

    也可以使用冒泡排序,或者系统自带的排序方法

    系统源码中,使用了快速排序

    我们需要实现 Interface 这个接口

    代码如下:

    package main
    
    import (
        "fmt"
        "math/rand"
        "sort"
    )
    
    // 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]
    }
    
    func main() {
        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)
        }
    }
    实现接口 vs 继承

    (C#中更多的是将接口于抽象类的比较)

    实现接口于继承有什么区别
    看下面一段代码:
    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 结构体需要扩展功能,同时不希望去破坏继承关系,则可以去实现某个接口即可,因此我们可以认为:实现接口是对继承机制的补充

    3、实现接口可以看作是对 继承的一种补充

    4接口和继承解决的解决的问题不同

      继承的价值主要在于:解决代码的复用性可维护性
      接口的价值主要在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。
    5、接口比继承更加灵活
      接口比继承更加灵活,继承是满足 is - a 的关系,而接口只需满足 like - a 的关系。
    6、接口在一定程度上实现代码解耦
    面向对象编程-多态
      变量(实例)具有多种形态。面向对象的第三大特征,在 Go 语言,多态特征是通过接口实现的。可以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。
    在前面的 Usb 接口案例,Usb usb ,既可以接收手机变量,又可以接收相机变量,就体现了 Usb接口 多态特性。
    //编写一个方法Working 方法,接收一个Usb接口类型变量
    //只要是实现了 Usb接口 (所谓实现Usb接口,就是指实现了 Usb接口声明所有方法)
    func (c Computer) Working(usb Usb) {// usb 变量会根据传进来的参数,来判断到底是Phone 还是Camear   usb接口变量就体现出多态的特点
        //通过usb接口变量来调用Start和Stop方法
        usb.Start()
        usb.Stop()
    }
    接口体现多态的两种形式
    1、多态参数
      在前面的 Usb 接口案例,Usb usb ,即可以接收手机变量,又可以接收相机变量,就体现了 Usb接口多态。
    2、多态数组
      给 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"
    
    func main() {
        var x interface{}
        var b2 float32 = 1.1
        x = b2 // 空接口,可以接收任何类型
        // x=> float32 【使用类型断言】
        y := x.(float32)
        fmt.Printf("y 的类型是 %T  值是 =%v", y, y)
    }

    对上面代码的说明

    1、在进行类型断言时,如果类型不匹配,就会报 panic, 因此进行类型断言时,要确保原来的空接口指向的就是断言的类型.

    2、如何在进行断言时,带上检测机制,如果成功就 ok,否则也不要报 panic

    func main() {
        var x interface{}
        var b2 float32 = 1.1
        x = b2 // 空接口,可以接收任何类型
        // x=> float32 【使用类型断言】
    
        //类型断言 带检测的
        if y, ok := x.(float32); ok {
            fmt.Println("convert ok...")
            fmt.Printf("y 的类型是 %T  值是 =%v
    ", y, y)
        } else {
            fmt.Println("convert fail...")
        }
        fmt.Println("go on ...")
    }

    类型断言的最佳实践

    在前面的 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 (c 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()
        }
    
    }

  • 相关阅读:
    第五周作业
    关于结对编程的理解
    第四周作业
    总结
    总结
    总结
    总结
    总结
    判断树、判断表
    总结
  • 原文地址:https://www.cnblogs.com/taotaozhuanyong/p/14714206.html
Copyright © 2020-2023  润新知