• Go语言 接口与类型


    Go语言

    12. 接口与类型

    接口是描述了一系列方法的集合。

    接口的使用是为了抽象,方便代码的解耦 → 写出更加优质、简洁、可复用的代码。

    12.1 接口定义

    12.1.1 接口的定义

    在Go语言中,接口(interface)是一个自定义类型。接口类型是一种抽象的类型,它不会暴露出它所代表的内部属性的结构,而只会展示出它自己的方法,因此,不能将接口类型实例化。

    根据Go语言规范,单个函数的接口命名为函数名加上“er”作为后缀,例如Reader、Writer、Formatter等。接口命名规范

    • 单个函数的接口名以“er”作为后缀,接口的实现则去掉“er”。
    • 两个函数的接口名综合两个函数名,以“er”作为后缀,接口的实现则去掉“er”。
    • 三个以上函数的接口,抽象这个接口的功能,类似于结构体命名。

    Go语言的接口是非常灵活的,通过一种方式来声明对象的行为,谁实现了这些行为,就相当于实现了这个接口里面声明各种方法的集合,但接口本身不去实现这些方法所需要的一些操作,因为这些方法没有被实现,所以它们是抽象的方法。

    type Animal interface {
       Name() string
       Speak() string
    }
    
    type Cat struct {
    }
    
    func (cat Cat) Name() string {
       return "Cat"
    }
    
    func (cat Cat) Speak() string {
       return "喵喵喵"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    定义了一个叫Animal的接口,结构体Cat实现了Animal接口的两个方法,因此可以认为,Cat实现了Animal接口。

    需要注意的是,与其他语言不同,Go语言的接口没有数据字段,只有定义的方法。

    12.1.2 鸭子类型

    在程序设计中,鸭子类型(duck typing)是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口来决定,而是由当前方法和属性的集合决定。

    对于Go来说,我们不关心对象是什么类型,或者到底是不是鸭子,我们只关心行为,关心它是如何使用的。

    12.2 接口的创建与实现

    Go语言接口是方法的集合,使用接口是实现模块化的重要方式。

    12.2.1 接口创建

    接口是用来定义行为类型的。这些被定义的行为不由接口直接实现,而是由用户自定义的类型实现,一个实现了这些方法的具体类型就叫作这个接口类型的实例。

    使用type和interface关键字即可创建一个接口。接口内部只需要声明存在什么方法,不需要实现这些方法。

    type InterfaceName interface{
        Method()
    } 
    
    • 1
    • 2
    • 3

    案例:数据库操作接口

    package main
    
    import (
       "errors"
       "fmt"
    )
    
    type IDatabaser interface {
       Connect() error
       Disconnect() error
    }
    
    //mysql数据库操作
    type Mysql struct {
       DBname    string
       isConnect bool
    }
    
    func (mysql *Mysql) Connect() error {
       fmt.Println("Mysql Connect DB =>" + mysql.DBname)
       mysql.isConnect = true
       if mysql.isConnect {
          fmt.Println("Mysql Connect Success!")
          return nil
       } else {
          return errors.New("Connect Failure!")
       }
    }
    
    func (mysql *Mysql) Disconnect() error {
       fmt.Println("Mysql disconnect success!")
       return nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    创建了一个对MySQL数据库操作的类型,这个类型实现了IDatabaser接口中的两个方法,因此Mysql类型就是IDatabaser的一个实例。

    再创建一个对Redis数据库操作的类型

    package main
    
    import (
       "errors"
       "fmt"
    )
    
    type IDatabaser interface {
       Connect() error
       Disconnect() error
    }
    
    //mysql数据库操作
    type Mysql struct {
       DBname    string
       isConnect bool
    }
    
    //Redis数据库操作
    type Redis struct {
       DBNname string
    }
    
    func (redis *Redis) Connect() error {
       fmt.Println("Redis connect DB =>" + redis.DBNname)
       fmt.Println("Redis connect success!")
       return nil
    }
    
    func (redis *Redis) Disconnect() error {
       fmt.Println("Redis disconnect success!")
       return nil
    }
    
    func (mysql *Mysql) Connect() error {
       fmt.Println("Mysql Connect DB =>" + mysql.DBname)
       mysql.isConnect = true
       if mysql.isConnect {
          fmt.Println("Mysql Connect Success!")
          return nil
       } else {
          return errors.New("Connect Failure!")
       }
    }
    
    func (mysql *Mysql) Disconnect() error {
       fmt.Println("Mysql disconnect success!")
       return nil
    }
    
    func main() {
       var mysql = Mysql{DBname: "student"}
       fmt.Println("开始连接")
       mysql.Connect()
       fmt.Println("断开连接")
       mysql.Disconnect()
    
       var redis = Redis{DBNname: "teacher"}
       fmt.Println("开始连接")
       redis.Connect()
       fmt.Println("断开连接")
       redis.Disconnect()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    在这里插入图片描述

    面向接口编程写法

    package main
    
    import (
       "errors"
       "fmt"
    )
    
    type IDatabaser interface {
       Connect() error
       Disconnect() error
    }
    
    //mysql数据库操作
    type Mysql struct {
       DBname    string
       isConnect bool
    }
    
    //Redis数据库操作
    type Redis struct {
       DBNname string
    }
    
    func (redis *Redis) Connect() error {
       fmt.Println("Redis connect DB =>" + redis.DBNname)
       fmt.Println("Redis connect success!")
       return nil
    }
    
    func (redis *Redis) Disconnect() error {
       fmt.Println("Redis disconnect success!")
       return nil
    }
    
    func (mysql *Mysql) Connect() error {
       fmt.Println("Mysql Connect DB =>" + mysql.DBname)
       mysql.isConnect = true
       if mysql.isConnect {
          fmt.Println("Mysql Connect Success!")
          return nil
       } else {
          return errors.New("Connect Failure!")
       }
    }
    
    func (mysql *Mysql) Disconnect() error {
       fmt.Println("Mysql disconnect success!")
       return nil
    }
    
    func HandleDB(db IDatabaser) {
       fmt.Println("开始连接")
       db.Connect()
       fmt.Println("断开连接")
       db.Disconnect()
    }
    
    func main() {
       var mysql = Mysql{DBname: "student"}
       HandleDB(&mysql)
    
       var redis = Redis{DBNname: "teacher"}
       HandleDB(&redis)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    在这里插入图片描述

    HandleDB()函数只有一个,却能实现处理多个不同类型的数据,这也称为Go的多态

    Go语言中,一个类型可以实现多个接口,反之,多个类型也可以实现同一个接口,任何一个类型都必然实现了空接口。

    12.2.2 接口赋值

    如果用户自定义的类型实现了某个接口类型所声明的一组方法,那么这个用户定义的类型的值就可以赋值给这个接口。这个赋值会把用户定义的类型的值存入接口类型的值。

    接口变量,接口变量存储了两部分信息,一是分配给接口变量的具体值(接口实现者的值),二是值的类型的描述器(接口实现者的类型)。

    接口赋值两种情况:

    • 将对象实例赋值给接口
    • 将一个接口赋值给另一个接口

    当一个对象的类型是一个接口的实例时,这个对象就可以赋值给这个接口。需要注意的是,只能将对象的指针赋值给接口变量,不能将对象值直接赋值给接口变量,否则就会发生错误。

    package main
    
    import (
       "errors"
       "fmt"
    )
    
    type IDatabaser interface {
       Connect() error
       Disconnect() error
    }
    
    //mysql数据库操作
    type Mysql struct {
       DBname    string
       isConnect bool
    }
    
    //Redis数据库操作
    type Redis struct {
       DBNname string
    }
    
    func (redis *Redis) Connect() error {
       fmt.Println("Redis connect DB =>" + redis.DBNname)
       fmt.Println("Redis connect success!")
       return nil
    }
    
    func (redis *Redis) Disconnect() error {
       fmt.Println("Redis disconnect success!")
       return nil
    }
    
    func (mysql *Mysql) Connect() error {
       fmt.Println("Mysql Connect DB =>" + mysql.DBname)
       mysql.isConnect = true
       if mysql.isConnect {
          fmt.Println("Mysql Connect Success!")
          return nil
       } else {
          return errors.New("Connect Failure!")
       }
    }
    
    func (mysql *Mysql) Disconnect() error {
       fmt.Println("Mysql disconnect success!")
       return nil
    }
    
    func main() {
       var redis = Redis{DBNname: "teacher"}
       var idb IDatabaser = &redis
       idb.Connect()
       idb.Disconnect()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    在这里插入图片描述

    第二种情况(通过一个接口给另一个接口赋值),在Go语言中只要两个接口拥有同样的方法集(次序不同不要紧),那么它们就是相同的,可以相互赋值。如果两个接口不是相同的,接口A的方法集是接口B方法集的子集,那么接口B可以赋值给接口A,反之则不成立。

    在这里插入图片描述

    IRediser接口的方法集是IDatabaser接口方法集的子集,IDatabaser接口变量就可以直接赋值给IRediser接口变量。

    12.3 接口嵌入

    接口嵌入,也叫接口组合,在其他语言中,这种接口的组合叫作继承;Go语言舍弃了繁杂的继承体系,但继承这个特性还是通过接口嵌入得以实现了。

    接口嵌入也就是指如果一个接口interface1作为interface2的一个嵌入字段,那么interface2隐式包含了interface1里面所有的方法。

    在这里插入图片描述

    接口嵌套需要注意:首先,一个接口类型只接受其他的接口类型的嵌入,嵌入结构体或其他类型则会报错;其次,一个接口类型不能嵌入自身,这包括直接嵌入和间接嵌入。

    错误案例

    ① 直接嵌套 在这里插入图片描述

    ② 间接嵌套 在这里插入图片描述

    案例

    package main
    
    import "fmt"
    
    type IPerson interface {
       Speak()
    }
    
    type IStudent interface {
       IPerson
       Study()
    }
    
    type ITeacher interface {
       IPerson
       Teach()
    }
    
    type Student struct {
       Name string
    }
    
    func (s *Student) Speak() {
       fmt.Println("My name is ", s.Name)
    }
    
    func (s *Student) Study() {
       fmt.Println(s.Name, "is studying")
    }
    
    type Teacher struct {
       Name string
    }
    
    func (t *Teacher) Speak() {
       fmt.Println("My name is ", t.Name)
    }
    
    func (t *Teacher) Teach() {
       fmt.Println(t.Name, "is teaching")
    }
    
    func main() {
       var stu Student = Student{"小王"}
       var teacher Teacher = Teacher{"小丁"}
    
       stu.Speak()
       stu.Study()
    
       teacher.Speak()
       teacher.Teach()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    在这里插入图片描述

    12.4 空接口

    12.4.1 将值保存到空接口

    空接口(interface{})是Go语言中最特殊的接口。在Java语言中,所有的类都继承自一个基类Object,而Go中的interface{}接口就相当于Java语言里的Object。

    在Go语言中,空接口不包含任何方法,也正因如此,所有的类型都实现了空接口,因此空接口可以存储任意类型的数值。

    package main
    
    import "fmt"
    
    func Log(name string, i interface{}) {
       fmt.Printf("Name = %s , Type = %T, value = %v\n", name, i, i)
    }
    
    func main() {
       var v1 interface{} = 1
       var v2 interface{} = "abc"
       var v3 interface{} = true
       var v4 interface{} = &v1
       var v5 interface{} = struct {
          Name string
       }{"小丁"}
       var v6 interface{} = &v5
    
       Log("v1", v1)
       Log("v2", v2)
       Log("v3", v3)
       Log("v4", v4)
       Log("v5", v5)
       Log("v6", v6)
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这里插入图片描述

    空接口可以存储数字、字符串、结构体、指针等任意类型的数值。

    12.4.2 从空接口取值

    【不能将空接口类型赋值到其他类型,如果需要的话必须使用类型断言】

    package main
    
    import "fmt"
    
    func main() {
       var a string = "abc"
       var i interface{} = a
       var b string = i.(string)
       fmt.Println(b)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    12.4.3 空接口的常见使用

    打印函数fmt.Println就使用了空接口

    func Println(a ...interface{}) (n int, err error)
    
    • 1

    interface{}前面的三个点代表的是可变长参数。可变长参数函数即其参数数量是可变的——0个或多个。声明可变参数函数的方式是在其参数类型前带上省略符(三个点)前缀,如果除了可变长参数外还有其他参数,则可变长参数必须放置在参数列表的末尾。

    func Printf(format string, a ...interface{}) (n int, err error)
    
    • 1
    package main
    
    import "fmt"
    
    func Log(args ...interface{}) {
       for num, arg := range args {
          fmt.Printf("Index => %d , value => %v\n", num, arg)
       }
    }
    
    func main() {
       s := make([]interface{}, 3)
       s[0] = 1
       s[1] = "abc"
       s[2] = struct {
          Num int
       }{1}
       //可变长参数
       fmt.Println("=======将切片拆散=========")
       Log(s...)
       fmt.Println("=======直接传入切片==========")
       Log(s)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    Log(s…)可以等同于Log(s[0], s[1], s[2])。

    12.5 类型断言

    类型断言是使用在接口变量上的操作。简单来说,接口类型向普通类型的转换就是类型断言。

    语法:

    t, ok := X.(T)
    
    • 1

    这里X表示一个接口变量,T表示一个类型(也可为接口类型),这句代码的含义是判断X的类型是否是T。如果断言成功,则ok为true,t的值为接口变量X的动态值;如果断言失败,则t的值为类型T的初始值,t的类型始终为T。

    package main
    
    import "fmt"
    
    func checkType(t interface{}, ok bool) {
       if ok {
          fmt.Println("断言成功")
       } else {
          fmt.Println("断言失败")
       }
       fmt.Printf("变量t的类型 = %T , 值 = %v\n", t, t)
    }
    
    func main() {
       var X interface{} = 1
       fmt.Println("第一次断言:")
       t0, ok := X.(string)
       checkType(t0, ok)
    
       fmt.Println("第一次断言:")
       t1, ok := X.(float64)
       checkType(t1, ok)
    
       fmt.Println("第一次断言:")
       t2, ok := X.(int)
       checkType(t2, ok)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述

    定义了一个接口变量X,接口变量的动态类型为int,动态值为1。第一次断言X的动态类型为string,断言失败,t0的类型为string,值为空字符串,所以没有打印出任何东西;断言类型为float64时,t1的值为0,也就是float64的初始值。当断言为int时,断言成功,t2的类型变为int,值为接口变量X的动态值1。

    类型断言两种情况:

    • 第一种,如果断言的类型T是一个具体类型,类型断言X.(T)就检查X的动态类型是否和T的类型相同。
    • 第二种,如果断言的类型T是一个接口类型,类型断言X.(T)检查X的动态类型是否满足T接口。

    第二种情况举例

    package main
    
    import "fmt"
    
    type Person interface {
       Speak()
    }
    
    type Student struct {
       Name string
    }
    
    func (s Student) Speak() {
       fmt.Println("My name is ", s.Name)
    }
    
    func checkType(t interface{}, ok bool) {
       if ok {
          fmt.Println("断言成功!")
       } else {
          fmt.Println("断言失败!")
       }
       fmt.Printf("变量t的类型 = %T, 值 = %v\n", t, t)
    }
    
    func main() {
       var student interface{} = Student{"小丁"}
       fmt.Println("第一次断言:")
       t0, ok := student.(string)
       checkType(t0, ok)
    
       fmt.Println("第二次断言:")
       t1, ok := student.(Person)
       checkType(t1, ok)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    在这里插入图片描述

    12.5.1 ok-pattern

    接口类型断言有两种方式,一种是ok-pattern,一种是switch-type。一般来说,当要断言的接口类型种类较少时,可以使用ok-pattern这种方式。

    if value, ok := 接口变量.(类型); ok == true {
        //接口变量是该类型时的处理
    }
    
    • 1
    • 2
    • 3
    package main
    
    import "fmt"
    
    type Person struct {
       Name string
       Age  int
    }
    
    func main() {
       s := make([]interface{}, 3)
       s[0] = 1
       s[1] = "str"
       s[2] = Person{"张三", 20}
    
       for index, data := range s {
          if value, ok := data.(int); ok == true {
             fmt.Printf("s[%d] Type = int,Value = %d\n", index, value)
          }
          if value, ok := data.(string); ok == true {
             fmt.Printf("s[%d] Type = string,Value = %s\n", index, value)
          }
          if value, ok := data.(Person); ok == true {
             fmt.Printf("s[%d] Type = Person, Person.Name = %v,Person.Age = %d\n", index, value.Name, value.Age)
          }
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述

    12.5.2 switch-type

    当要断言的接口类型种类较多时,使用ok-pattern方式就显得很累赘,代码非常冗余,这时我们可以使用switch-type来进行批量判断。

    switch value := 接口变量.(type) {
        case类型1:
        // 接口变量是类型1时的处理
        case类型2:
        // 接口变量是类型2时的处理
        case类型3:
        // 接口变量是类型3时的处理
        ...
        default:
        // 接口变量不是所有case中所列举的类型时的处理
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    package main
    
    import "fmt"
    
    type Person struct {
       Name string
       Age  int
    }
    
    func main() {
       s := make([]interface{}, 3)
       s[0] = 1
       s[1] = "str"
       s[2] = Person{"张三", 20}
    
       for index, data := range s {
          switch value := data.(type) {
          case int:
             fmt.Printf("s[%d] Type = int, Value = %d\n", index, value)
          case string:
             fmt.Printf("s[%d] Type = string, Value = %s\n", index, value)
          case Person:
             fmt.Printf("s[%d] Type = Person, Person.Name = %v,Person.Age = %d\n", index, value.Name, value.Age)
          }
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这里插入图片描述

    12.7 知识拓展

    非侵入式接口

    Go语言的接口是非侵入式的。

    侵入式接口与非侵入式接口的区别:

    • 侵入式接口:需要显式地创建一个类去实现一个接口。
    • 非侵入式接口:不需要显式地创建一个类去实现一个接口。

    非侵入式接口的三个好处:

    • 去掉了繁杂的继承体系,Go语言的标准库再也不需要绘制类库的继承树图。在Go中,类的继承树并无意义,只需要知道这个类实现了哪些方法、每个方法是何含义就足够了。
    • 实现类的时候,只需要关心自己应该提供哪些方法,不用再纠结接口需要拆得多细才合理。接口由使用方按需定义,而不用事前规划。
    • 不用为了实现一个接口而导入一个包,因为多引用一个外部的包,就意味着更多的耦合。

    总的来说,非入侵式的接口设计更简洁、灵活,更注重实用性。

    package main
    
    import "fmt"
    
    type IPhoner interface {
       Call() error
       Video() error
       Game() error
    }
    
    type Apple interface {
       Call() error
       Video() error
    }
    
    type Huawei interface {
       Call() error
       Game() error
    }
    
    type Phone struct {
       Name string
    }
    
    func (p *Phone) Call() error {
       fmt.Println(p.Name, "Start call")
       return nil
    }
    
    func (p *Phone) Video() error {
       fmt.Println(p.Name, "Start vedio")
       return nil
    }
    
    func (p *Phone) Game() error {
       fmt.Println(p.Name, "Start Game")
       return nil
    }
    
    func main() {
       var apple Apple = &Phone{"apple"}
       var huawei Huawei = &Phone{"huawei"}
    
       apple.Call()
       huawei.Game()
       
       
       
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    在这里插入图片描述

    另一种写法

    在这里插入图片描述

  • 相关阅读:
    工厂如何实现智能制造?有哪些需要注意的?
    laravel8-rabbitmq消息队列-实时监听跨服务器消息
    Ubuntu22.04如何开机重新自动运行脚本
    InnoDB数据页结构(1)环境搭建
    Java版企业工程项目管理系统源码+java版本+项目模块功能清单+spring cloud +spring boot
    Stable Diffusion 2.0 来了
    技术杂文:群晖上Docker版SVN服务器从搭建到访问的全程记录
    29 WEB漏洞-CSRF及SSRF漏洞案例讲解
    总在用户态调试 C# 程序,终还是搭了一个内核态环境
    WPS Word自动编号转文本
  • 原文地址:https://blog.csdn.net/weixin_44226181/article/details/125908739