日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Golang interface 接口详解

發(fā)布時間:2023/12/18 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Golang interface 接口详解 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言

在之前的文章中我們說過,golang 是通過 結(jié)構(gòu)體(struct)-方法(method)-接口(interface) 的組合使用來實現(xiàn)面向?qū)ο蟮乃枷搿T谇拔?Golang 復合類型 和 Golang method 方法詳解 已經(jīng)詳細介紹過 struct 和 method,本文將介紹 golang 面向?qū)ο蟮牧硪粋€重要組成部分:接口(interface)。

文章目錄

  • 前言
  • 接口
    • 接口概念
    • 接口定義
    • 封裝性
    • 接口查詢
    • 接口賦值
      • 將對象實例賦值給接口
      • 將一個接口賦值給另一個接口
    • 接口組合
  • Any 類型
  • sort interface 示例

接口

接口概念

接口是一種抽象的類型,描述了一系列方法的集合,作用是對一系列具有聯(lián)系的方法做出抽象和概括。接口只定義方法名和參數(shù),而不包含具體的實現(xiàn),這種抽象的方式可以讓程序變得更加靈活更加通用。

在很多語言中,接口都是侵入式的,侵入式接口的意思是實現(xiàn)類需要明確聲明自己實現(xiàn)了某個接口,這就帶來了一個很矛盾的問題,比如 A 調(diào)用了 B 的接口,那么 A 一定會希望接口被設計成自己想要使用的樣子,但是 B 才是接口的實現(xiàn)方,基于模塊設計的單向依賴原則,B 在實現(xiàn)自身的業(yè)務時,不應該關(guān)心某個具體使用方的要求,一個接口被定義的時候,并不知道自己的方法會被誰實現(xiàn),也不知道會被怎么樣實現(xiàn)。因此,侵入式接口一直是面向?qū)ο缶幊讨幸粋€經(jīng)常遭受質(zhì)疑的特性。

不同的是,golang 的接口是一種 非侵入式 的接口,一個類型不需要明確聲明,只要實現(xiàn)了接口的所有方法,這個類型就實現(xiàn)了該接口,這個類型的對象就是這個接口類型的實例。 因此,在 golang 中,不再需要定義類的繼承關(guān)系,而且在定義接口時候,只需要關(guān)心自己需要提供哪些方法,其他的方法有使用方按需定義即可。

接口定義

/* 定義接口 */ type interface_name interface {method_name1(input_paras...) [return_type]method_name2(input_paras...) [return_type]method_name3(input_paras...) [return_type] }/* 定義結(jié)構(gòu)體 */ type struct_name struct {/* variables */ }/* 實現(xiàn)接口方法 */ func (struct_name_variable struct_name) method_name1(input_paras...) [return_type] {/* 方法實現(xiàn) */ }func (struct_name_variable struct_name) method_name2(input_paras...) [return_type] {/* 方法實現(xiàn)*/ }func (struct_name_variable struct_name) method_name3(input_paras...) [return_type] {/* 方法實現(xiàn)*/ }

go語言的源碼中大量使用到了接口,比如說在前面的文章中多次使用到的 error 類型

// The error built-in interface type is the conventional interface for // representing an error condition, with the nil value representing no error. type error interface {Error() string }

封裝性

接口是 golang 封裝性的重要一環(huán),接口可以封裝具體類型和類型的值,即使一個類型還有別的方法,接口的實例也只能調(diào)用接口暴露出來的方法。如下:

type HelloInterface interface {Hello() }type User struct { }func (f *User) Hello() {fmt.Println("hello") }func (f *User) Bye() {fmt.Println("bye") }func InterfaceTest() {u := &User{}u.Hello() // oku.Bye() // okvar user HelloInterface = new(User) // 接口實例化user.Hello() // okuser.Bye() // Compile error: user.Bye undefined (type HelloInterface has no field or method Bye) }

注意,使用一個接口對象必須要先實例化,否則接口對象的值為 nil,調(diào)用 nil 對象的任何方法都會產(chǎn)生空指針 panic。

接口查詢

和查詢某個元素是否在 map 中類似,Golang 也內(nèi)置了接口查詢,可以使用和 map 類似的語法來檢查對象實例是否實現(xiàn)了接口,如下:

var user HelloInterface = new(User) // 接口實例化 if u1, ok := user.(HelloInterface); ok {fmt.Println(u1) // yes }if u2, ok := user.(Reader); ok {fmt.Println(u2) // no }

也可以查詢對象是否是某個類型

if u3, ok := user.(*User); ok {fmt.Println(u3) }

Golang 還可以使用斷言和反射來進行類型查詢,這兩個內(nèi)容會在后續(xù)的文章中介紹。

接口賦值

將對象實例賦值給接口

要將對象實例賦值給接口,要求該對象實例實現(xiàn)了接口要求的所有方法。如:

type Integer intfunc (a Integer) Less(b Integer) bool {return a < b } func (a *Integer) Add(b Integer) {*a += b }type LessAdder interface {Less(b Integer) boolAdd(b Integer) }var a Integer = 1 var b LessAdder = &a

注意,此處賦值時用 &a 而不是 a, 因為 Go 會自動為 *Integer 生成一個新的 Less 方法

func (a *Integer) Less(b Integer) bool { return (*a).Less(b) }

從而讓 *Integer 既存在 Less(),又存在 Add(), 滿足接口 LessAdder

將一個接口賦值給另一個接口

在Go語言中,只要兩個接口擁有相同的方法列表(不用考慮順序),那么它們就是等同的,可以相互賦值。

package onetype ReadWriter1 interface {Read(buf []byte) (n int, err error) Write(buf []byte) (n int, err error) }// 第二個接口位于另一個包中: package twotype ReadWriter2 interface {Write(buf []byte) (n int, err error) Read(buf []byte) (n int, err error) }// 可以相互賦值 var file1 two.ReadWriter2 = new(File) var file2 one.ReadWriter1 = file1 var file3 two.ReadWriter2 = file2

接口賦值并不要求兩個接口必須等價。如果接口 A 的方法列表是接口 B 的方法列表的子集, 那么接口 B可以賦值給接口 A,但是 A 不可以賦值給 B。(大接口可以賦值給小接口)

接口組合

類似于結(jié)構(gòu)內(nèi)嵌,接口的組合也是使用匿名機制實現(xiàn)的,如下:

type Reader interface {Read(p []byte) (n int, err error) }type Writer interface {Write(p []byte) (n int, err error) }// 將 Read 和 Write 方法組合 // ReadWriter 接口既能做 Reader 接口的所有事情,又能做 Writer 接口的所有事情。type ReadWriter interface {ReaderWriter }// 與下面的寫法完全等價 type ReadWriter interface {Read(p []byte) (n int, err error) Write(p []byte) (n int, err error) }

Any 類型

Go語言中任何對象實例都滿足空接口 interface{},所以可以把 interface{} 看作可以指向任何對象的 Any 類型,當函數(shù)可以接受任意的對象實例時,我們會將其聲明為 interface{},從而可以接受任意類型的對象,然后再使用類型斷言來對該參數(shù)進行轉(zhuǎn)換,再做后續(xù)的處理(具體內(nèi)容參看類型斷言的博客)。

最典型的例子是標準庫 fmt 中 PrintXXX 系列的函數(shù),例如:

func Printf(fmt string, args ...interface{}) func Println(args ...interface{})

sort interface 示例

接下來,讓我們通過介紹內(nèi)置的 sort 包來加深一下對接口的理解,順便了解一下這個常用包的使用。

Golang 的 sort 包中通過接口的方式內(nèi)置了可以對任何類型的列表進行快排的功能,下面我們一起來看看它是如何使用的。

首先我們要先了解 sort.Interface 源碼中定義了哪些方法:

// A type, typically a collection, that satisfies sort.Interface can be // sorted by the routines in this package. The methods require that the // elements of the collection be enumerated by an integer index. type Interface interface {// Len is the number of elements in the collection.Len() int// Less reports whether the element with// index i should sort before the element with index j.Less(i, j int) bool// Swap swaps the elements with indexes i and j.Swap(i, j int) }

可以看到,我們需要先定義三個方法:

  • 計算列表長度的方法
  • 比較兩個元素的方法
  • 交換兩個元素的方法
  • 因此,我們需要定義一種類型,這種類型要同時具有以上三種方法,比如一個簡單的 Student 類

    type Student struct {ID int64Name string }type StudentSlice []*Studentfunc (s StudentSlice) Len() int {return len(s) }func (s StudentSlice) Less(i, j int) bool {return s[i].ID < s[j].ID }func (s StudentSlice) Swap(i, j int) {s[i], s[j] = s[j], s[i] }

    接下來,對一個 Student 進行初始化

    s1 := &Student{ID: 1,Name: "A",}s2 := &Student{ID: 2,Name: "B",}s3 := &Student{ID: 3,Name: "C",}students := []*Student{s3, s1, s2}

    準備工作已經(jīng)做好,接下來我們先來看一下 sort.Sort 函數(shù)的源碼

    // Sort sorts data. // It makes one call to data.Len to determine n, and O(n*log(n)) calls to // data.Less and data.Swap. The sort is not guaranteed to be stable. func Sort(data Interface) {n := data.Len()quickSort(data, 0, n, maxDepth(n)) }

    可以看到,函數(shù)的入?yún)⑹且粋€ sort.Interface 類型的對象,然后對這個對象進行快排操作,所以,要使用這個函數(shù),我們還需要把 []*Student 類型轉(zhuǎn)換成 StudentSlice,由于 StudentSlice 實現(xiàn)了 sort.Interface 的所有方法,所以 StudentSlice 的對象就是 sort.Interface 類型的對象。

    sort.Sort(StudentSlice(students))

    完成后,打印 students,我們就可以看到排好序的列表了。

    {ID:1 Name:A} {ID:2 Name:B} {ID:3 Name:C}

    對于自定義的類型,要進行排序就要完成上述的所有操作,幸運的是,對于常用基本類型,go 源碼已經(jīng)為我們準備好了一系列可以直接調(diào)用的方法。

    // Ints sorts a slice of ints in increasing order. func Ints(a []int) { Sort(IntSlice(a)) }// Float64s sorts a slice of float64s in increasing order // (not-a-number values are treated as less than other values). func Float64s(a []float64) { Sort(Float64Slice(a)) }// Strings sorts a slice of strings in increasing order. func Strings(a []string) { Sort(StringSlice(a)) }// IntsAreSorted tests whether a slice of ints is sorted in increasing order. func IntsAreSorted(a []int) bool { return IsSorted(IntSlice(a)) }// Float64sAreSorted tests whether a slice of float64s is sorted in increasing order // (not-a-number values are treated as less than other values). func Float64sAreSorted(a []float64) bool { return IsSorted(Float64Slice(a)) }// StringsAreSorted tests whether a slice of strings is sorted in increasing order. func StringsAreSorted(a []string) bool { return IsSorted(StringSlice(a)) }

    我們可以直接使用這些方法對基本類型 slice 進行排序,如:

    ids := []int{5,1,7,1,3,8,7,4} names := []string{"qqq", "www", "ee", "aa", "rr", "ba"}sort.Ints(ids) sort.Strings(names)fmt.Println(ids) // [1 1 3 4 5 7 7 8] fmt.Println(names) // [aa ba ee qqq rr www]

    總結(jié)

    以上是生活随笔為你收集整理的Golang interface 接口详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。