1 接口是什么

Golang中没有像Python、Java拥有类和对象的概念,其封装对象或说明对象是通过接口来实现的。比如谁能够实现什么样的功能,便能够将其抽象化封装。

接口定义了一组方法(抽象方法集,不包括该方法的具体实现细节),注意不能包含变量。


通过如下格式定义Golang接口:

type Namer interface {
Method1(param_list) return_type1
Method2(param_list) return_type1
Method2(param_list) (return_type1, return_type2)
...
}

上面的Namer就是一个接口类型,是一种抽象类型

如果要实现一个接口就需要实现该接口类型定义中的所有方法

2 实现接口

如果一个类型实现了一个接口中要求的所有方法,那么这个类型实现了这个接口。

例如在fmt包下的io.writer接口

package io
// Implementations must not retain p.
type Writer interface {
// write从p向底层数据流写入len(p)个字节的数据
// 返回实际写入的字节数n,且0<=n<=len(p)
Write(p []byte) (n int, err error)
}

fmt.Printffmt.Sprintf用于实现字符串的格式化,前者用于格式化输出至控制台(os.Stdout),后者把结果以string类型返回.

package fmt

// 接收一个io.writer类型形参
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintf(format, a)
n, err = w.Write(p.buf)
p.free()
return
} // 返回Fprintf调用
func Printf(format string, a ...interface{}) (n int, err error) {
// w 为os.Stdout
return Fprintf(os.Stdout, format, a...)
} func Sprintf(format string, a ...interface{}) string {
// newPrinter申请一个临时对象池,sync.Pool
p := newPrinter()
// 格式匹配及处理输出格式
p.doPrintf(format, a)
// 将缓冲区数据取出并字符串化
s := string(p.buf)
// 清空缓存区
p.free()
return s
}

os.Stdout返回了一个*File类型,该类型实现了io.Writer接口中的write方法,即os.Stdout是一个io.Writer类型

func (f *File) write(b []byte) (n int, err error) {
n, err = f.pfd.Write(b)
runtime.KeepAlive(f)
return n, err
}

实现接口的例子1:

package main

// 统计输入流字节数
type ByteCounter int func (b *ByteCounter) Write (p []byte) (n int, err error){
*b += ByteCounter(len(p))
return len(p), nil
} func main() {
var counter ByteCounter
counter = 0
testStr = "ByteConter"
fmt.Fprintf(&counter, "%s", testStr)
fmt.Print(&counter) // “11”
}

例子 2:

// 文件数据写入接口
type DataWriter interface {
DataWrite(data []byte) (n int, err error)
} type File struct {
fname string
fmode int
path string
} func (file *File) DataWrite(data []byte) (n int, err error) {
fw, err := os.OpenFile(file.path, os.O_RDWR, 6)
if err != nil{
log.Fatalf("%v", err)
return 0, err
}
defer fw.Close()
n, werr := fw.Write(data)
if werr != nil {
log.Fatalf("%v", err)
return 0, werr
}
fmt.Println("write successfully!")
return n, nil
} func main() { var f File
f = File{
fname: "h.txt",
fmode: 6,
path: "./h.txt",
}
}

实现关系在 Go语言中是隐式的。两个类型之间的实现关系不需要在代码中显式地表示出来。Go语言中没有类似于 implements 的关键字。 Go编译器将自动在需要的时候检查两个类型之间的实现关系。

***总结: **

  1. 接口的方法与实现接口的类型方法格式(方法中的名称、参数列表、返回参数列表)一致。

  2. 必须实现该接口的所有抽象方法。

  3. 一个类型可以实现多个接口,例如socket同时实现了io.Closerio.Writer接口。

  4. 多个类型可以实现相同的接口。

    Golang的接口实现是隐式实现的,无需让实现接口的类型显示表示出实现了那些接口。这个涉及被称为非侵入式涉及

3 嵌套接口

一个接口可以包含一个或多个其他的接口,这相当于直接将内嵌接口的方法列举在外层接口中的一样。

4 类型断言

Java当中有instanceof这样的关键字判断类型 Go当中自然也有相应的方法来判断类型 ,类型断言是作用在接口值上的操作,写出来类似于x.(T),其中x是一个接口类型的表达式,T则是一个具体类型(称为断言类型)


如果断言类型T是一个具体类型,那么类型断言就会检查x的动态类型是否为T。如果检查成功,则类型断言的结果就是x的动态类型

写法为value, ok := em.(T) 如果确保em 是同类型的时候可以直接使用value:=em.(T)一般用于switch语句中。

变量名 含义
em 代表要判断的变量
T 代表要判断的变量
value 代表返回的类型
ok 代表是否为同一类型

*注意:

  1. em必须为interface类型才可以进行类型断言;如果不是则可以使用interface{}来强转。

  2. 当函数作为参数并且被调用函数将参数类型指定为interface{}的时候是没有办法直接调用该方法的。

    type FuncTyte func(s string) int
    
    func PrintFunc(s string) int{
    fmt.Println(s)
    return 1
    } func JudgeFuncType(v interface{}){
    //FuncTyte(v)("hello") # 报错 需要先判断v的类型是否是FuncType
    //fmt.Println(reflect.TypeOf(v))
    if _func, ok := v.(FuncTyte); ok{
    _func("hello")
    }
    } func main() {
    JudgeFuncType(FuncTyte(PrintFunc))
    }

例子:

// w是一个接口类型
var w io.Writer
// os.Stdin是一个*os.File类型,该类型实现了io.Writer接口中的Write方法
w = os.Stdin
// *os.File是一个具体的引用类型,而非接口类型
f := w.(*os.File) // 成功 f==os,Stdin
fmt.Print(f, reflect.TypeOf(f)) // &{0xc00006e000} *os.File
k := w.(*bufio.Reader) // 报错,因为w是一个*os.File类型

如果断言类型T是一个接口类型,那么类型断言就会检查x的动态类型是否满足T, 即x要实现T中所有抽象方法。成功则返回x

例子:

var w io.Writer
w = os.Stdout
f1 := w.(io.ReadWriter) // 成功, *os.File类型(w)实现了io.ReadWriter接口
f2 := w.(io.ReadWriteCloser) // 成功
f3 := w.(io.ByteReader) // 报错, *os.File类型(w)未实现io.ReadByte接口

除此之外,如果类型A实现了接口B,注意看到底是指针类型A还是非指针类型A实现的

package main

import "fmt"
type A interface {
IsA(b []byte) int
}
type Aa struct {
name string
}
func (sq Aa) IsA(b []byte) int {
return 1
}
func main() {
s1 := &Aa{"aa"}
s2 := Aa{"aa"}
fmt.Printf("S1:\t%T\n", interface{}(s1).(A)) // S1: *main.Aa
fmt.Printf("S2:\t%T", interface{}(s2).(A)) // S2: main.Aa
}

比如上述是Aa类型实现了接口A, 类型断言结果默认获取的是其动态类型,是指针就是指针,是非指针就是非指针,但是如果是*Aa类型实现了接口A,其非指针类型进行类型断言就会出现运行时错误“interface conversion: main.Aa is not main.A: missing method IsA”,一般对于基本数据类型使用非指针来实现接口,其余类型尽量使用指针。

5 空接口类型

一个类型如果实现了接口中所有方法,则称该类型实现了该接口;那同样,任何类型都隐式地实现了空接口。

空接口能够保存任何类型的值,也可以从空接口中取出值

空接口类型就类似于java中的Object对象,任何实现类的超类。

var a interface{} = 1
var b int = a.(int) // 必须类型断言, 否则出错
fmt.Println(b)

此外,值得注意的是,对于空接口类型的值为动态类型的是不可比较的.

动态类型:map、slice

非动态类型:channal、int、string、[10]int{}、函数、struct

var a interface{} = []int{1, 2, 3}
var b interface{} = []int{3, 4, 5}
fmt.Println(a==b)
//panic: runtime error: comparing uncomparable type []int

6 空接口类型实现字典

空接口可以保存任何类型这个特性可以方便地用于容器的设计。下面例子使用 map 和 interface{} 实现了一个字典。字典在其他语言中的功能和 map 类似,可以将任意类型的值做成键值对保存,然后进行找回、遍历操作。详细实现代码如下所示。

package main

// 字典结构
type Dictionary struct {
data map[interface{}]interface{}
} // 创建字典
func NewDictionary() *Dictionary{
dict := &Dictionary{}
dict.Clear()
return dict
} // 清空字典
func (dict *Dictionary) Clear() {
dict.data = make(map[interface{}]interface{})
} // Set
func (dict *Dictionary) Set(key , value interface{}){
dict.data[key] = value
} // Get
func (dict *Dictionary) Get(key interface{}) interface{}{
return dict.data[key]
} // 遍历(使用回调方式)
func (dict *Dictionary) Visit(callback func(k,v interface{}) bool){ if callback == nil{
return
} else {
for key, value := range dict.data{
if !callback(key, value) {
return
}
}
}
}

7 类型分支(type-switch)

type-switch流程控制的语法与switch-case流程控制代码块有些相似,

一个type-switch流程控制代码块的语法如下所示:

switch 接口变量.(type) {
case 类型1:
// 变量是类型1时的处理
case 类型2:
// 变量是类型2时的处理

default:
// 变量不是所有case中列举的类型时的处理
}
var w io.Writer
w = os.Stdout
switch w.(type) {
case *os.File:
fmt.Println("os.file")
case nil:
fmt.Println("nil")
default:
fmt.Println("error")
}

一个例子:

// Alipay手机支付
type Alipay struct {
money string
} // cash现金支付
type Cash struct {
money string
} // 支持刷脸支付
type PayUseFaceID interface {
UseFaceID() (success int, err error)
} // 使用假票情况
type UseArtifficialMoney interface {
UseFakeMoney() (flag int)
} // Alipay支持面容支付
func (alipay Alipay) UseFaceID() (success int, err error) {
return 1, nil
} // 现金支付可能会使用假币
func (cash *Cash) UseFakeMoney() (flag int){
return 1
} func judgeMethod(method interface{}){
switch method.(type) {
case PayUseFaceID:
fmt.Println("alipay 刷脸支付")
case UseArtifficialMoney:
fmt.Println("现金支付使用假币")
default:
fmt.Println("不支持的方式")
}
} func main() { judgeMethod(new(Alipay))
fmt.Println("--------------")
judgeMethod(new(Cash)) } //alipay 刷脸支付
//--------------
//现金支付使用假币

在这个例子中定义了两种支付方式的类型,一种是Alipay,另一种是Cash,同时又定义了两种特征的接口,第一个是刷脸支付PayUseFaceID特征,另一个是使用假币UseArtifficialMoney的特征。这里,Alipay类型实现了接口PayUseFaceID

8 空接口和函数重载

在 Go语言中函数重载可以用可变参数 ...T 作为函数最后一个参数来实现。如果我们把 T 换为空接口,那么可以知道任何类型的变量都是满足 T (空接口) 类型的,这样就允许我们传递任何数量任何类型的参数给函数,即重载的实际含义。

9 接口继承

在golang中,接口被设计为实现多态的最佳方式,

当一个类型包含(内嵌)另一个类型(实现了一个或多个接口)的指针时,这个类型就可以使用(另一个类型)所有的接口方法。

Golang 接口的更多相关文章

  1. 【Golang 接口自动化06】微信支付md5签名计算及其优化

    前言 可能看过我博客的朋友知道我主要是做的支付这一块的测试工作.而我们都知道现在比较流行的支付方式就是微信支付和支付宝支付,当然最近在使用低手续费大力推广的京东金融(已改名为京东数科)以后也可能站到第 ...

  2. 【Golang 接口自动化00】为什么要用Golang做自动化?

    为什么使用Golang做自动化 顺应公司的趋势学习了Golang之后,因为没有开发那么多的时间和项目来实践,怕步此前学习Java缺少练习遗忘殆尽的后尘,决定利用工作之余的时间把此前用Python的写的 ...

  3. Golang 接口与反射知识要点

    目录 Golang 接口与反射知识要点 1. 接口类型变量 2. 类型断言 3. 鸭子类型 4. 反射机制 5. reflect 包 TypeOf().ValueOf() Type().Kind() ...

  4. Golang接口(interface)三个特性(译文)

    The Laws of Reflection 原文地址 第一次翻译文章,请各路人士多多指教! 类型和接口 因为映射建设在类型的基础之上,首先我们对类型进行全新的介绍. go是一个静态性语言,每个变量都 ...

  5. golang接口三个特性

    类型和接口 因为映射建设在类型的基础之上,首先我们对类型进行全新的介绍.go是一个静态性语言,每个变量都有静态的类型,因此每个变量在编译阶段中有明确的变量类型,比如像:int.float32.MyTy ...

  6. Golang接口简单了解

    在Golang中,一个类只需要实现了接口要求的所有函数,我们就说这个类实现了该接口. package main import "fmt" type Animal interface ...

  7. 【Golang 接口自动化08】使用标准库httptest完成HTTP请求的Mock测试

    前言 Mock是一个做自动化测试永远绕不过去的话题.本文主要介绍使用标准库net/http/httptest完成HTTP请求的Mock的测试方法. 可能有的小伙伴不太了解mock在实际自动化测试过程中 ...

  8. 【Golang 接口自动化03】 解析接口返回XML

    上一篇我们学习了怎么发送各种数据类型的http请求,这一篇我们来介绍怎么来解析接口返回的XML的数据. 解析接口返回数据 定义结构体 假设我们现在有一个接口返回的数据resp如下: <?xml ...

  9. 【Golang 接口自动化02】使用标准库net/http发送Post请求

    写在前面 上一篇我们介绍了使用 net/http 发送get请求,因为考虑到篇幅问题,把Post单独拎了出来,我们在这一篇一起从源码来了解一下Golang的Post请求. 发送Post请求 net/h ...

随机推荐

  1. ffmpeg命令参数详解

    ffmpeg命令参数详解 http://linux.51yip.com/search/ffmpeg ffmpeg图片加滤镜效果 参考:https://cloud.tencent.com/develop ...

  2. Django--模型层进阶

    目录 QuerySet对象 可切片 可迭代 惰性查询 缓存机制 何时查询集不会被缓存? exists()与iterator()方法 exists() iterator() 中介模型 查询优化 表数据 ...

  3. ASP.NET 后台 COOKIE 的设置

    这几年经常与安全打交道,深知 COOKIE 对一个网站的安全影响有多大,所以在编写相与 cookie 相关代码的时候,都会特别的小心. 最近做一个系统,有几个地方用到 cookie, 然后统一把 co ...

  4. 使用node+vue实现简单的WebSocket聊天功能

    最近学习了一下websocket的即时通信,感觉非常的强大,这里我用node启动了一个服务进行websocket链接,然后再vue的view里面进行了链接,进行通信,废话不多说,直接上代码吧, 首先, ...

  5. adb shell get/setprop, setenforce...

    adb shell getprop <key> 获取设备参数信息adb shell setprop <key> <value> 设置设备参数信息 例子1:>C ...

  6. php与阿里云短信接口接入

    使用阿里云短信API,需要在控制台获取以下必要参数,其中需要自己手机验证+官方审核多次,尤其审核需要保持耐心. 1. accessKeyId  相当于你的个人账户密钥: 2. accessKeySec ...

  7. FormData使用方法详解

    FormData的主要用途有两个: 1.将form表单元素的name与value进行组合,实现表单数据的序列化,从而减少表单元素的拼接,提高工作效率. 2.异步上传文件 一.创建formData对象 ...

  8. 【Spring Cloud】Spring Cloud之Zipkin server搭建以及RabbitMQ收集,分布式服务跟踪(3)

    一.搭建步骤 1)新建Spring Boot项目,引入pom坐标 <parent> <groupId>org.springframework.boot</groupId& ...

  9. Python 去除文件中的空行

    def clear_space(): with open("test","r",encoding="utf-8") as fr: for l ...

  10. c#时间与时间戳互转13位

    Unix时间戳(Unix timestamp),或称Unix时间(Unix time).POSIX时间(POSIX time),是一种时间表示方式,定义为从格林威治时间1970年01月01日00时00 ...