IT教程 ·

Go言语基本之接口(面向对象编程下)

一起了解 .Net Foundation 项目 No.7

1 接口

1.1 接口引见

接口(interface)是Go言语中中心部份,Go言语供应面向接口编程,那末接口是什么?

现实生活中,有许多接口的例子,比方说电子设备上的充电接口,这个充电接口醒目什么,在接口设想时就定义好了,比方说这个接口既能充电可以举行数据的传输;以后只需电子设备是完成这个接口的功用,就像手机上的Type-C接口既可以充电又可以数据传输。

在Golang中接口(interface)是一种范例,一种笼统的范例。在接口范例中可以定义一组要领,然则这些不需要完成。而且interface不能包括任何变量。关于某个自定义范例可以运用这个接口,然则必需完成这个接口中定义的一切要领。从这点来看,接口不体贴事物的属性,只体贴事物具有的行动。

1.2 为何要运用接口

type Cat struct{}

func (c Cat) Say() string { return "喵喵喵" }

type Dog struct{}

func (d Dog) Say() string { return "汪汪汪" }

func main() {
    c := Cat{}
    fmt.Println("猫:", c.Say())
    d := Dog{}
    fmt.Println("狗:", d.Say())
}

上面的代码中定义二楼猫和狗,它们都会叫,都有Say()这个要领,你会发明main函数中显著有反复的代码,无果后续再有其他动物的话,如许的代码还会一向反复下去。那我们能不能如许考虑问题呢,把这些动物归结成“能叫的动物“,只是差别的动物有差别的叫法。

像相似的例子在编程中常常碰到:

比方一个网上商城大概运用付出宝、微信、银联等体式格局在线付出,那能不能把它们当做“付出体式格局“来处置惩罚呢?

再比方三角形、四边形、圆形都能盘算周长和面积,那能不能把它们当做“图形”来处置惩罚呢?

关于上面的这些问题,Go言语中供应了接口范例。当看到一个接口范例的值是,我们不晓得它是什么,唯一晓得的是经由历程它的要领能做什么。

1.3 基础语法

type 接口名 interface {
    method1(参数列表) 返回值列表
    method2(参数列表) 返回值列表
}

//完成接口一切要领
func (t 自定义范例) method1(参数列表) 返回值列表 {
    //要领完成
}
func (t 自定义范例) method2(参数列表) 返回值列表 {
    //要领完成
}

接口里的一切要领都没有要领体,即接口的要领都是没有完成的要领。接口表现了程序设想的多态和高内聚低耦合的头脑。

Golang中的皆苦,不需要显式的完成,只需一个变量,含有接口范例中的一切要领,那末这个变量就完成了这个接口。

1.4 接口的运用案例

关于1.2中的示例,运用接口完成:

//定义一个Sayer的接口
type Sayer interface {
    Say()
}

//定义两个构造体Cat和Dog
type Cat struct {}
type Dog struct {}

// Dog完成了Sayer接口
func (d Dog) Say() {
    fmt.Println("汪汪")
}

// Cat完成了Sayer接口
func (c cat) Say() {
    fmt.Println("喵喵")
}

那完成了接口有什么用?

接口范例变量可以存储一切完成了该接口的实例。比方上面的示例中,Sayer范例的变量可以存储DogCat范例的变量。

func main() {
    var x Sayer  //声明一个Sayer范例的变量x
    a := Cat{}
    b := Dog{}
    x = a       //只要自定义范例完成了某个接口,才将自定义范例的变量赋值给接口范例变量
    x.Say()
    x = b
    x.Say()
}

1.5 值范例吸收者和指针吸收者完成接口的区分

经由历程下面的例子来看下两者的区分:

//声明一个Mover的接口
type Mover interface {
    move()
}

//声明一个dog的构造体
type dog struct {}

1.5.1 值范例吸收者完成接口

值范例吸收者dog完成Mover接口:

func (d dog) move() {
    fmt.Println("狗会动")
}

func main() {
    var x Mover
    var dog1 = dog{}
    x = dog1
    
    var dog2 = &dog{}   
    x = dog2            //x可以吸收*dog范例
    x.move
}

从上面的代码可以看出,关于struct假如吸收者是值范例,不管是构造体照样构造体指针范例的变量,只需这个构造体完成对应的接口,那末这两个变量都可以赋值给该接口变量。因为Go言语中有对指针范例变量求值的语法糖,*dog2等价于dog2

1.5.2 指针范例吸收者完成接口

指针范例的吸收者*dog完成接口:

func (d *dog) move() {
    fmt.Println("狗会动")
}

func main() {
    var x Mover
    var dog1 = dog{}
    x = dog1            //x不可以吸收dog范例,会报dog范例没有完成Mover接口
    
    var dog2 = &dog{}   
    x = dog2            //x可以吸收*dog范例
    x.move
}

从上面的示例可知,假如struct的吸收者是指针范例,只能将构造体指针赋值给接口范例的变量。

1.5.3 面试题

下面代码可否编译?为何?

type People interface {
    Speak(string) string
}

type Student struct{}

func (stu *Student) Speak(think string) (talk string) {
    if think == "sb" {
        talk = ""
    } else {
        talk = "您好"
    }
    return
}

func main() {
    var peo People = Student{}
    think := ""
    fmt.Println(peo.Speak(think))
}

1.6 空接口

1.6.1 空接口的定义

空接口是指没有定义任何要领的接口,因而任何范例都完成了空接口。

空接口范例的变量可以存储恣意范例的变量:

func main() {
    // 定义一个空接口x
    var x interface{}
    s := "Hello viktor"
    x = s
    fmt.Printf("type:%T value:%vn", x, x)
    i := 100
    x = i
    fmt.Printf("type:%T value:%vn", x, x)
    b := true
    x = b
    fmt.Printf("type:%T value:%vn", x, x)
}

1.6.2 空接口的运用

运用空接口可以吸收恣意范例的函数参数:

// 空接口作为函数参数
func show(a interface{}) {
    fmt.Printf("type:%T value:%vn", a, a)
}

运用空接口可以保留恣意值的字典:

// 空接口作为map值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "viktor"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)

1.7 接口总结及注意事项

  • 接口自身不能建立实例,然则可以指向一个完成了该接口的自定义范例的变量(参考1.5中的示例);
  • 接口中的一切的要领都没有要领体,即都是没有完成的要领;
  • 在Golang中,一个自定义范例需要将某个接口的一切要领都完成,则称这个自定义范例完成了该接口;
  • 一个自定义范例只要完成了某个接口,才将该自定义范例的实例(变量)赋给接口范例;
  • 一个自定义范例可以完成多个接口;
  • Golang接口中不能有任何变量;
  • 一个接口(比方A接口)可以嵌套多个别的接口(比方B,C接口),这时候假如要完成A接口,必需将B,C接口的要领也悉数完成;
// Sayer 接口
type Sayer interface {
    say()
}

// Mover 接口
type Mover interface {
    move()
}

// 接口嵌套
type animal interface {
    Sayer
    Mover
}

//cat构造体完成animal接口
type cat struct {
    name string
}

func (c cat) say() {
    fmt.Println("喵喵喵")
}

func (c cat) move() {
    fmt.Println("猫会动")
}

func main() {
    var x animal
    x = cat{name: "花花"}
    x.move()
    x.say()
}
  • interface范例默许是一个指针(援用范例),假如没有对interface初始化就运用,那末就会输出nil
  • 空接口interface{}没有任何要领,所以一切范例都 完成了空接口,即我们可以把任何一个变量赋给空接口。

2 接口 vs 继承

从一个事物的角度来看,比方一个篮球运动员或许大门生:

篮球运动员或许大门生可以离别继承运动员或许门生的一些属性;然则,篮球运动员或许大门生有大概会有一些雷同的行动,比方会说英语,那末就可以定义一个会说英语的接口,离别让两者完成接口。

接口和继承的区分:

  • 接口和继承处理的问题差别:继承的代价重要在于,处理代码的复用性和可维护性;接口的代价重要在于,设想,设想好种种范例(要领),让别的自定义范例去完成这些要领;
  • 接口比继承越发天真。继承是满足is - a关联,而接口只需满足like - a的关联;
  • 接口在肯定程度上完成代码解耦。

3 面向对象编程-多态

3.1 基础引见

变量(实例)具有多种形状。面向对象的第三大特征,在Go言语中,多态特征是经由历程接口完成的。可以根据一致的接口来挪用差别的完成。这时候接口变量就显现差别的形状。

3.2 疾速入门案例

//声明/定义一个接口
type Usb interface {
    //声清楚明了两个没有完成的要领
    Start() 
    Stop()
}

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) 
}

在上面的代码中,Working(usb Usb)要领,既可以吸收手机变量,又可以吸收相机变量,就表现了Usb接口多态的特征

3.3 接口表现多态的两种情势

多态参数

在一个函数或许是一个要领的参数假如是一个接口范例,那末该参数可以吸收完成了该接口的一切的自定义范例。如3.2中的案例。

多态数组

自定义范例只需完成了接口,那末都可以寄存在接口的数组中。看以下案例:

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)

}

4 范例断言

4.1 基础引见

在前面的一切示例中,都是将一个变量(示例)赋值给一个接口。因为一个接口可以被多个自定义范例完成,我们都晓得在Go言语中差别范例之前是不能赋值的,此时与如许的一个需求,将接口范例的变量赋值给自定义范例的变量,比方下面的代码,该怎样完成?

type Point struct {
    x, y int
}

func main() {
    var a interface{}
    var point Point = Point{1,2}
    a = point   //ok
    //怎样将a赋给一个Point变量?
    var b Point
    b = a   //?
    fmt.Println(b)
}

类比:可以将接口明白成一个很大的容器,当把一个自定义范例赋给接口,就相当于把它放入这个大容器内里,因为这个容器内里可以放许多差别的自定义范例,当想要把适才的谁人放入容器内里的自定义范例赋给别的的自定义范例,就需要先找到它。这个找的历程就是范例断言。

范例断言的基础语法:

x.(T)

个中:

  • x:示意范例为interface{}的变量
  • T:示意断言x大概是的范例

该语法返回两个参数,第一个参数是x转化为T范例后的变量,第二个值是一个布尔值,若为true则示意断言胜利,为false则示意断言失利。

对上面代码的革新:

type Point struct {
    x, 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)  //范例断言,示意推断a是不是指向Point范例的变量,假如是就转成Point范例并赋给b变量,不然报错
    fmt.Println(b)
}

4.2 范例断言的运用

范例断言,因为接口是平常范例,不晓得详细范例,假如要转成详细范例,就需要运用范例断言,详细以下:

func main() {
    var x interface{}
    var b float32 = 6.6
    x = b2      //空接口,可以吸收恣意范例
    //x -> flaot32 [运用范例断言]
    y := x.(float32)
    fmt.Printf("y的范例是 %T 值是%vn", y, y)
}

关于上面代码,在举行范例断言时,假如范例不婚配,就会报panic,因而举行范例短延时,要确保本来的空接口指向的就是断言的范例。

断言时也可以带上检测机制,以下示例 :

func main() {
    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("继承实行...")
}

4.3 范例断言的最好实践

轮回推断传入参数的范例:

//编写一个函数,可以推断输入的参数是什么范例
func TypeJudge(items... interface{}) {
    for index, x := range items {
        switch x.(type) {
            case bool :
                fmt.Printf("第%v个参数是 bool 范例,值是%vn", index, x)
            case float32 :
                fmt.Printf("第%v个参数是 float32 范例,值是%vn", index, x)
            case float64 :
                fmt.Printf("第%v个参数是 float64 范例,值是%vn", index, x)
            case int, int32, int64 :
                fmt.Printf("第%v个参数是 整数 范例,值是%vn", index, x)
            case string :
                fmt.Printf("第%v个参数是 string 范例,值是%vn", index, x)
            default :
                fmt.Printf("第%v个参数是  范例 不确定,值是%vn", 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

    TypeJudge(n1, n2, n3, name, address, n4, stu1, stu2)
}

Head First设计模式——代理模式

参与评论