前言
在之前的文章中我們說過,golang 是通過 結構體(struct)-方法(method)-介面(interface) 的組合使用來實作面向物件的思想,在前文 Golang 復合型別 和 Golang method 方法詳解 已經詳細介紹過 struct 和 method,本文將介紹 golang 面向物件的另一個重要組成部分:介面(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() // ok
u.Bye() // ok
var user HelloInterface = new(User) // 介面實體化
user.Hello() // ok
user.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 int
func (a Integer) Less(b Integer) bool {
return a < b
}
func (a *Integer) Add(b Integer) {
*a += b
}
type LessAdder interface {
Less(b Integer) bool
Add(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 one
type ReadWriter1 interface {
Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
}
// 第二個介面位于另一個包中:
package two
type 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 {
Reader
Writer
}
// 與下面的寫法完全等價
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 int64
Name string
}
type StudentSlice []*Student
func (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]
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/278928.html
標籤:區塊鏈
上一篇:vue3 解決 cnpm install 報錯 Cannot find module ‘vue-loader-v16/package.json
