go☞009接口

阅读量: zyh 2020-10-09 19:15:11
Categories: > Tags:

前言

接口和结构体一样,也是一种自定义数据类型,它是抽象类型。

接口内只是定义了实现这个接口的具体类型所需要的方法名,并不处理方法的逻辑。

✨这就如同你制作一艘飞船(具体类型),需要一个蓝图(接口)来告诉你飞船需要实现哪些功能(方法)。

✨只不过飞船(具体类型)的这些功能(方法)如何制作,蓝图(接口)不管,由你自己去实现。

所以,当一个数据类型实现了接口里的所有方法定义,就意味着这个数据类型实现了接口。

总结:若多个数据类型都拥有同样的功能,且功能逻辑一致。为了方便的调用这个功能,我们就可以将功能里的方法定义为一个接口,并将功能(变量 数据类型)替换为功能(变量 接口类型)。使用时,功能(变量 接口类型)可以直接传入数据类型变量。

✨变量 接口类型 == main.变量 数据类型

接口类型是引用类型,本质是一个指针。

什么时候用

你写了一个程序(功能),它通过 type-c 接口接收设备(例如结构)数据(结构属性)并判断这些数据是否异常。

但是拥有 type-c 接口的设备种类很多,每一个设备都是一个结构对象,所以你不可能自己声明一堆设备结构类型并为每一个设备写一个判断程序,因此你的程序只接收一个 type-c 接口类型作为形参。即:只要你的设备有type-c接口(结构已实现 type-c 接口的方法),我就允许你传入。

用法

如何定义接口?

定义接口,接口包含方法名。

方法名必须唯一,且不能为空。

type <inter_name> interface {
    <method_name1> [return_type]
    <method_name2> [return_type]
    ...
}

如何满足接口?

定义结构

type <struct_name> struct {
    var var_name1 var_type
}

实现接口方法

// 实现方法1,例如充电
func (self <struct_name>) <method_name1>() [return_type]{
    ...
}
// 实现方法2,例如数据传输
func (self <struct_name>) <method_name2>() [return_type]{
    ...
}

接口可以嵌套

例子1

package main

import "fmt"

// typec 接口
type typeC interface {
    chongdian()
    hdmi()
}

// 结构
type diannao struct {
    PinPai string
    TypeC int
}
func (self diannao) chongdian() {
    if self.TypeC == 1 {
        fmt.Printf("%s电脑可以充电\n", self.PinPai)
    }
}
func (self diannao) hdmi() {
    if self.TypeC == 2{
        fmt.Printf("%s电脑既可以充电,也可以HDMI\n", self.PinPai)
    }
}

type shouji struct {
    PinPai string
    TypeC int
}
func (self shouji) chongdian() {
    if self.TypeC == 1 {
        fmt.Printf("%s手机可以充电\n", self.PinPai)
    }
}
func (self shouji) hdmi() {
    if self.TypeC == 2{
        fmt.Printf("%s手机既可以充电,也可以HDMI\n", self.PinPai)
    }
}

func TypeCInfo(j typeC){
    j.chongdian()
    j.hdmi()
}

func main(){
    d1 := diannao{"sanxing", 2}
    s1 := shouji{"huawei", 1}
    TypeCInfo(d1)
    TypeCInfo(s1)
}

输出:

sanxing电脑既可以充电,也可以HDMI
huawei手机可以充电

例子2

自定义输出

package main

import "fmt"

type Person struct {
    Name, Country string
}

func (p Person) String() string {
    return fmt.Sprintf("%v is from %v", p.Name, p.Country)
}
func main() {
    rs := Person{"John Doe", "USA"}
    ab := Person{"Mark Collins", "United Kingdom"}
    fmt.Printf("%s\n%s\n", rs, ab)
}
John Doe is from USA
Mark Collins is from United Kingdom

从 fmt.Printf(“%s\n%s\n”, rs, ab) 以及输出结果可以得知,fmt.Printf()(通用功能)接收一个未知接口,这个未知接口定义了 String() 方法。

Person 结构实现了 String() 方法,因此可以直接传入 fmt.Printf()

所以,我们可以通过自定义 String() 方法,来输出自定义字符串

经查证,这个未知接口的代码是:

type Stringer interface {
    String() string
}

列子3

通过重写io.Copy(),只输出 respone 数据中想要的部分.

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
)

type GitHubResponse []struct { // 通过解析 respone json 数据,定义新的 respone 结构
    FullName string `json:"full_name"`
}

type customWriter struct{}

func (w customWriter) Write(p []byte) (n int, err error) { // 重写 io.Copy第一个接口参数的 write 方法
    var resp GitHubResponse
    json.Unmarshal(p, &resp)
    for _, r := range resp {
        fmt.Println(r.FullName)
    }
    return len(p), nil
}

func main() {
    resp, err := http.Get("https://api.github.com/users/microsoft/repos?page=15&per_page=5")
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(1)
    }

    writer := customWriter{}
    io.Copy(writer, resp.Body) // 传递实现了接口的 customWriter 数据类型变量
}

例子4

通过浏览器访问,输出数据

package main

import (
    "fmt"
    "log"
    "net/http"
)

type dollars float32

func (d dollars) String() string {
    return fmt.Sprintf("$%.2f", d)
}

type database map[string]dollars

func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    for item, price := range db {
        fmt.Fprintf(w, "%s: %s\n", item, price)
    }
}

func main() {
    db := database{"Go T-Shirt": 25, "Go Jacket": 55}
    log.Fatal(http.ListenAndServe("localhost:8000", db))
}

思考?

当某个功能函数A的形参argsA是一个接口类型的时候,

情况一:如果满足接口的数据类型B,在实现方法的时候,方法接收者(数据类型本身)是,则可以直接将数据类型B的变量传递给功能函数A,也可以将&B传递给A,也可以将接口变量(已指向&B)传递给A;又因为是值传递,所以方法内针对接收者的修改,并不影响方法外的接收者本身。

情况二:如果满足接口的数据类型B,在实现方法的时候,方法接收者(数据类型本身)是指针,则只能将&B传递给A,或者将接口变量(已指向&B)传递给A;又因为是指针传递,所以方法内针对接收者的修改,会影响到方法外的接收者本身。

最后,关于情况一,其实go是强类型,只不过指针可以传入的原因是go解析器底层帮你修正了代码。

例如,sort.Sort(data Interface) ,它接收sort.Interface类型,因此只要确保排序的数据对象实现了sort.Interface定义的Len(),Less(),Swap()方法,那么直接传入你需要排序的数据对象即可。

那么,为什么TypeCInfo(j typeC) 明明接收一个interface,却可以接收一个满足interface的数据类型。

因为golang在底层帮你改了,实际的代码是:

    var myd1 typeC
    d1 := diannao{"sanxing", 2}
    s1 := shouji{"huawei", 1}
    myd1 = &d1
    TypeCInfo(myd1)
    myd1 = &s1
    TypeCInfo(myd1)