• Golang 接口


    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
    ", interface{}(s1).(A))  // S1:	*main.Aa
    	fmt.Printf("S2:	%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中,接口被设计为实现多态的最佳方式,

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

  • 相关阅读:
    泛在电力物联网建设路线
    如何建设泛在电力物联网?
    泛在电力物联网到底该怎么建?
    泛在电力物联网(能源互联网+物联网)浅析
    泛在电力物联网分析—架构形式
    泛在电力物联网:两个业务 两种发展逻辑
    国网“泛在电力物联网”的战略与逻辑
    MVC中使用Hangfire按秒执行任务
    hangfire 实现已完成的job设置过期,防止数据无限增长
    解决ASP.NET Core部署到IIS,更新项目"另一个程序正在使用此文件,进程无法访问"
  • 原文地址:https://www.cnblogs.com/kisun168/p/11575498.html
Copyright © 2020-2023  润新知