Golang interface 接口详解
前言
在之前的文章中我們說過,golang 是通過 結構體(struct)-方法(method)-接口(interface) 的組合使用來實現面向對象的思想。在前文 Golang 復合類型 和 Golang method 方法詳解 已經詳細介紹過 struct 和 method,本文將介紹 golang 面向對象的另一個重要組成部分:接口(interface)。
文章目錄
- 前言
- 接口
- 接口概念
- 接口定義
- 封裝性
- 接口查詢
- 接口賦值
- 將對象實例賦值給接口
- 將一個接口賦值給另一個接口
- 接口組合
- Any 類型
- sort interface 示例
接口
接口概念
接口是一種抽象的類型,描述了一系列方法的集合,作用是對一系列具有聯系的方法做出抽象和概括。接口只定義方法名和參數,而不包含具體的實現,這種抽象的方式可以讓程序變得更加靈活更加通用。
在很多語言中,接口都是侵入式的,侵入式接口的意思是實現類需要明確聲明自己實現了某個接口,這就帶來了一個很矛盾的問題,比如 A 調用了 B 的接口,那么 A 一定會希望接口被設計成自己想要使用的樣子,但是 B 才是接口的實現方,基于模塊設計的單向依賴原則,B 在實現自身的業務時,不應該關心某個具體使用方的要求,一個接口被定義的時候,并不知道自己的方法會被誰實現,也不知道會被怎么樣實現。因此,侵入式接口一直是面向對象編程中一個經常遭受質疑的特性。
不同的是,golang 的接口是一種 非侵入式 的接口,一個類型不需要明確聲明,只要實現了接口的所有方法,這個類型就實現了該接口,這個類型的對象就是這個接口類型的實例。 因此,在 golang 中,不再需要定義類的繼承關系,而且在定義接口時候,只需要關心自己需要提供哪些方法,其他的方法有使用方按需定義即可。
接口定義
/* 定義接口 */ type interface_name interface {method_name1(input_paras...) [return_type]method_name2(input_paras...) [return_type]method_name3(input_paras...) [return_type] }/* 定義結構體 */ type struct_name struct {/* variables */ }/* 實現接口方法 */ func (struct_name_variable struct_name) method_name1(input_paras...) [return_type] {/* 方法實現 */ }func (struct_name_variable struct_name) method_name2(input_paras...) [return_type] {/* 方法實現*/ }func (struct_name_variable struct_name) method_name3(input_paras...) [return_type] {/* 方法實現*/ }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 封裝性的重要一環,接口可以封裝具體類型和類型的值,即使一個類型還有別的方法,接口的實例也只能調用接口暴露出來的方法。如下:
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,調用 nil 對象的任何方法都會產生空指針 panic。
接口查詢
和查詢某個元素是否在 map 中類似,Golang 也內置了接口查詢,可以使用和 map 類似的語法來檢查對象實例是否實現了接口,如下:
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 還可以使用斷言和反射來進行類型查詢,這兩個內容會在后續的文章中介紹。
接口賦值
將對象實例賦值給接口
要將對象實例賦值給接口,要求該對象實例實現了接口要求的所有方法。如:
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。(大接口可以賦值給小接口)
接口組合
類似于結構內嵌,接口的組合也是使用匿名機制實現的,如下:
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 類型,當函數可以接受任意的對象實例時,我們會將其聲明為 interface{},從而可以接受任意類型的對象,然后再使用類型斷言來對該參數進行轉換,再做后續的處理(具體內容參看類型斷言的博客)。
最典型的例子是標準庫 fmt 中 PrintXXX 系列的函數,例如:
func Printf(fmt string, args ...interface{}) func Println(args ...interface{})sort interface 示例
接下來,讓我們通過介紹內置的 sort 包來加深一下對接口的理解,順便了解一下這個常用包的使用。
Golang 的 sort 包中通過接口的方式內置了可以對任何類型的列表進行快排的功能,下面我們一起來看看它是如何使用的。
首先我們要先了解 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}準備工作已經做好,接下來我們先來看一下 sort.Sort 函數的源碼
// 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)) }可以看到,函數的入參是一個 sort.Interface 類型的對象,然后對這個對象進行快排操作,所以,要使用這個函數,我們還需要把 []*Student 類型轉換成 StudentSlice,由于 StudentSlice 實現了 sort.Interface 的所有方法,所以 StudentSlice 的對象就是 sort.Interface 類型的對象。
sort.Sort(StudentSlice(students))完成后,打印 students,我們就可以看到排好序的列表了。
{ID:1 Name:A} {ID:2 Name:B} {ID:3 Name:C}對于自定義的類型,要進行排序就要完成上述的所有操作,幸運的是,對于常用基本類型,go 源碼已經為我們準備好了一系列可以直接調用的方法。
// 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]總結
以上是生活随笔為你收集整理的Golang interface 接口详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 和领导相处的10条法则,越早知道越好
- 下一篇: 对网络地图思考