GO语音继承 接口 多态| 青训营笔记

84 阅读5分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记

继承

继承实现方法

在Go中,继承采用匿名结构体实现。

type Student struct{
    Name string
    Age int
}
​
type Graduate struct{
    Student  // 继承Student
    id int
}
​
type PostGraduates struct{
    Student  // 继承Student
    Tutor string
}

继承的细节

  • 结构体和嵌入的匿名结构体中,有相同的字段和方法时,编译器采用就近原则。要访问匿名结构体的字段时,加上匿名结构体来区分
type A struct{
    Name string
}
​
type B struct{
    A  // 继承A
    Name string
}
func main() {
    var b B
    b.A.Name = "marry"
    
    fmt.Println(b.Name)  // 空串
    fmt.Println(b.A.Name)  // marry
    
    b.Name = "smith"
    fmt.Println(b.Name)  // smith
}
  • 嵌入匿名结构体可以使用指针
type Person struct{
    Name string
    Age int
}
​
type Info struct{
    Id int
    Score int
}
​
type Student struct{
    *Person
    *Info  // 多重继承,但并不推荐(多重继承使继承关系混乱)
}
​
func main() {
    stu := Student{
        &Person{"john", 18},
        &Info{001, 90},
    }
}
  • 嵌入多个匿名结构体,可以实现多重继承。因为多重继承使得继承关系混乱,所以并不推荐多重继承

接口

若多个类型都有一个或者多个共同点,那么就可以将这些共同的抽象出来聚合在一起,形成接口。接口与现实中的接口类似,如USB接口,USB接口可以插入手机、鼠标、键盘等设备,只要符合USB标准即可。

接口实现了高内聚,低耦合的思想

接口实现方法

Go中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现了这个接口。一般定义方法如下:

type InterfaceName interface{
    // method1(参数利列表) 返回值列表
    // method2(参数利列表) 返回值列表
    // ...
}

下面抽象的定义一个USB接口

type Usb interface{  // 定义一个USB接口
    Work()
    Stop()
}
​
type Phone struct{  // 手机
    // ...
}
​
func (p Phone) Work() {
    // ...
}
​
func (p Phone) Stop() {
    // ...
}
​
type Mouse struct{  // 鼠标
    // ...
}
​
func (m Mouse) Work() {
    // ...
}
​
func (m Mouse) Stop() {
    // ...
}
​
type Computer struct {
    // ...
}
​
func (c Computer) Working(usb Usb) {
    usb.Start() // USB接口开始工作
    
    if phone, ok := usb.(Phone); ok {   // 类型断言
        // 若是手机,执行一些特殊操作
        // ...
    }
    
    usb.Stop()  // USB接口停止工作
}
​
func main() {
    computer := Computer{}
    phone := Phone{}
    mouse := Mouse{}
    
    computer.Working(phone)  // 接口“插入手机”
    computer.Working(mouse)  // 接口“插入鼠标”
}

接口的细节

  • 接口的实现是指,一个类型实现了被定义接口内的所有方法(隐式实现)。一个自定类型可以实现多个接口
  • 接口不能创建变量,但可以指向一个实现了该接口的自定义类型(不仅仅是结构体)的实例
type A interface{
    test()
}
​
type Student struct{
    Name string
}
​
// Student 实现了A接口
func (stu Student) test() {
    fmt.Println("Stu test()")
}
​
func main() {
    var stu Student  // Stu 实现了A接口
    var a A = stu
    a.test()  // "Stu test()"
}
  • 接口之间可以有继承关系,要实现所有被继承的接口
type B interface{
    test1()
}
​
type C interface{
    test2()
}
​
type A interface{
    B  // 继承B接口
    C  // 继承C接口
    test3()
}
​
​
type Student struct {
    Name string
}
​
// 用Student实现A接口:要实现所有被继承的接口func (stu Student) test1() {
    //...
}
​
func (stu Student) test2() {
    //...
}
​
func (stu Student) test3() {
    //...
}
  • interface是引用类型(指针)
  • 空接口interface{}没有实现任何方法,所以所有类型都实现了空接口,可以把任何变量都赋值给空接口
type T interface{
    // 空接口
}
​
func main() {
    num1 := 6.17
    num2 := 6
    
    var t1 T = num1
    fmt.Println(t1) // 6.17
    
    var t2 interface{} = num2 // interface{}同样表示空接口
    fmt.Println(t2) // 6
}

实践:实现Interface接口,对结构体切片进行排序

一个满足sort.Interface接口的(集合)类型可以被本包的函数进行排序。

type Interface interface {
    // Len方法返回集合中的元素个数
    Len() int
    // Less方法报告索引i的元素是否比索引j的元素小
    Less(i, j int) bool
    // Swap方法交换索引i和j的两个元素
    Swap(i, j int)
}

以Hero结构体实现Interface接口,对Hero的年龄进行排序:

package main
​
import (
    "fmt"
    "math/rand"
    "sort"
)
​
type Hero struct {
    Name string
    Age  int
}
​
// HeroSlice 是Hero结构体的切片类型
type HeroSlice []Hero
​
func (hs HeroSlice) Len() int {
    return len(hs)
}
​
// Less 方法就是决定用什么标准排序
// 对Hero的年龄从小到大排序
func (hs HeroSlice) Less(i, j int) bool {
    return hs[i].Age < hs[j].Age
}
​
func (hs HeroSlice) Swap(i, j int) {
    hs[i], hs[j] = hs[j], hs[i]
}
​
func main() {
    var heroes HeroSlice
​
    for i := 0; i < 5; i++ {
        hero := Hero{
            Name: fmt.Sprintf("英雄-%d", rand.Intn(100)),
            Age:  rand.Intn(150),
        }
        heroes = append(heroes, hero)
    }
​
    // 排序前的顺序
    fmt.Println("========排序前========")
    for _, v := range heroes {
        fmt.Println(v)
    }
​
    // 利用sort.Sort排序
    sort.Sort(heroes)
​
    fmt.Println("========排序后========")
    for _, v := range heroes {
        fmt.Println(v)
    }
}
​
/*
========排序前========
{英雄-81 87}          
{英雄-47 59}          
{英雄-81 18}          
{英雄-25 140}         
{英雄-56 0}           
========排序后========
{英雄-56 0}           
{英雄-81 18}          
{英雄-47 59}          
{英雄-81 87}          
{英雄-25 140}   
*/

接口和继承的关系

  • 接口的实现可以看作对继承的补充

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

    • 继承:解决代码复用性可维护性
    • 接口设计,设计和各种规范(方法),让其他自定义类型去实现这些方法
  • 接口比继承更灵活

    • 继承是is-a的关系
    • 接口只需满足like-a的关系
  • 接口在一定程度上实现了代码的解耦

多态

在GO中,多态是通过接口实现的。可以安装统一的接口调用不同的实现,这是接口变量就程序不同的形态。

接口体现多态

2种形式如下:

  • 多态参数:以接口作为函数的参数

  • 多态数组:接口数组可以存放任何实现了该接口的变量

    • 空接口数组任何类型都可以存放

类型断言

使用方法

由于接口是一般类型,不知道具体类型,若要从接口转换成具体类型,就需要类型断言

var z float32 = 1.1
var x interface{}   // 空接口
x = z   // 空接口可以接收任意类型
y, ok := x.(float32)    // 类型断言,y=1.1if ok {
    // 断言成功
} else {
    // 断言失败
}
​
// 不论断言成功与否,代码继续执行
// ....

\