欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Golang中interface接口的原理和使用技巧

 更新時間:2022年11月29日 16:04:56   作者:AllenWu  
interface?接口在?Go?語言里面的地位非常重要,是一個非常重要的數據結構。本文主要介紹了Golang中interface接口的原理和使用技巧,希望對大家有所幫助

一、Go interface 介紹

interface 在 Go 中的重要性說明

interface 接口在 Go 語言里面的地位非常重要,是一個非常重要的數據結構,只要是實際業(yè)務編程,并且想要寫出優(yōu)雅的代碼,那么必然要用上 interface,因此 interface 在 Go 語言里面處于非常核心的地位。

我們都知道,Go 語言和典型的面向對象的語言不太一樣,Go 在語法上是不支持面向對象的類、繼承等相關概念的。但是,并不代表 Go 里面不能實現面向對象的一些行為比如繼承、多態(tài),在 Go 里面,通過 interface 完全可以實現諸如 C++ 里面的繼承 和 多態(tài)的語法效果。

interface 的特性

Go 中的 interface 接口有如下特性:

關于接口的定義和簽名

  • 接口是一個或多個方法簽名的集合,接口只有方法聲明,沒有實現,沒有數據字段,只要某個類型擁有該接口的所有方法簽名,那么就相當于實現了該接口,無需顯示聲明了哪個接口,這稱為 Structural Typing。
  • interface 接口可以匿名嵌入其他接口中,或嵌入到 struct 結構中
  • 接口可以支持匿名字段方法

關于接口賦值

  • 只有當接口存儲的類型和對象都為 nil 時,接口才等于 nil
  • 一個空的接口可以作為任何類型數據的容器
  • 如果兩個接口都擁有相同的方法,那么它們就是等同的,任何實現了他們這個接口的對象之間,都可以相互賦值
  • 如果某個 struct 對象實現了某個接口的所有方法,那么可以直接將這個 struct 的實例對象直接賦值給這個接口類型的變量。

關于接口嵌套,Go 里面支持接口嵌套,但是不支持遞歸嵌套

通過接口可以實現面向對象編程中的多態(tài)的效果

interface 接口和 reflect 反射

在 Go 的實現里面,每個 interface 接口變量都有一個對應 pair,這個 pair 中記錄了接口的實際變量的類型和值(value, type),其中,value 是實際變量值,type 是實際變量的類型。任何一個 interface{} 類型的變量都包含了2個指針,一個指針指向值的類型,對應 pair 中的 type,這個 type 類型包括靜態(tài)的類型 (static type,比如 int、string...)和具體的類型(concrete type,interface 所指向的具體類型),另外一個指針指向實際的值,對應 pair 中的 value。

interface 及其 pair 的存在,是 Go 語言中實現 reflect 反射的前提,理解了 pair,就更容易理解反射。反射就是用來檢測存儲在接口變量內部(值value;類型concrete type) pair 對的一種機制。

二、Go 里面為啥偏向使用 Interface

Go 里面為啥偏向使用 Interface 呢? 主要原因有如下幾點:

可以實現泛型編程(雖然 Go 在 1.18 之后已經支持泛型了)

在 C++ 等高級語言中使用泛型編程非常的簡單,但是 Go 在 1.18 版本之前,是不支持泛型的,而通過 Go 的接口,可以實現類似的泛型編程,如下是一個參考示例

    package sort

    // 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)
    }
    
    ...
    
    // 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) {
        // Switch to heapsort if depth of 2*ceil(lg(n+1)) is reached.
        n := data.Len()
        maxDepth := 0
        for i := n; i > 0; i >>= 1 {
            maxDepth++
        }
        maxDepth *= 2
        quickSort(data, 0, n, maxDepth)
    }

Sort 函數的形參是一個 interface,包含了三個方法:Len(),Less(i,j int),Swap(i, j int)。使用的時候不管數組的元素類型是什么類型(int, float, string…),只要我們實現了這三個方法就可以使用 Sort 函數,這樣就實現了“泛型編程”。

這種方式,我在閃聊項目里面也有實際應用過,具體案例就是對消息排序。 下面給一個具體示例,代碼能夠說明一切,一看就懂:

    type Person struct {
    Name string
    Age  int
    }
    
    func (p Person) String() string {
        return fmt.Sprintf("%s: %d", p.Name, p.Age)
    }
    
    // ByAge implements sort.Interface for []Person based on
    // the Age field.
    type ByAge []Person //自定義
    
    func (a ByAge) Len() int           { return len(a) }
    func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
    func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
    
    func main() {
        people := []Person{
            {"Bob", 31},
            {"John", 42},
            {"Michael", 17},
            {"Jenny", 26},
        }
    
        fmt.Println(people)
        sort.Sort(ByAge(people))
        fmt.Println(people)
    }

可以隱藏具體的實現

隱藏具體的實現,是說我們提供給外部的一個方法(函數),但是我們是通過 interface 接口的方式提供的,對調用方來說,只能通過 interface 里面的方法來做一些操作,但是內部的具體實現是完全不知道的。

例如我們常用的 context 包,就是這樣設計的,如果熟悉 Context 具體實現的就會很容易理解。詳細代碼如下:

    func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
        c := newCancelCtx(parent)
        propagateCancel(parent, &c)
        return &c, func() { c.cancel(true, Canceled) }
    }

可以看到 WithCancel 函數返回的還是一個 Context interface,但是這個 interface 的具體實現是 cancelCtx struct。

        // newCancelCtx returns an initialized cancelCtx.
        func newCancelCtx(parent Context) cancelCtx {
            return cancelCtx{
                Context: parent,
                done:    make(chan struct{}),
            }
        }
        
        // A cancelCtx can be canceled. When canceled, it also cancels any children
        // that implement canceler.
        type cancelCtx struct {
            Context     //注意一下這個地方
        
            done chan struct{} // closed by the first cancel call.
            mu       sync.Mutex
            children map[canceler]struct{} // set to nil by the first cancel call
            err      error                 // set to non-nil by the first cancel call
        }
        
        func (c *cancelCtx) Done() <-chan struct{} {
            return c.done
        }
        
        func (c *cancelCtx) Err() error {
            c.mu.Lock()
            defer c.mu.Unlock()
            return c.err
        }
        
        func (c *cancelCtx) String() string {
            return fmt.Sprintf("%v.WithCancel", c.Context)
        }

盡管內部實現上下面三個函數返回的具體 struct (都實現了 Context interface)不同,但是對于使用者來說是完全無感知的。

    func WithCancel(parent Context) (ctx Context, cancel CancelFunc)    //返回 cancelCtx
    func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) //返回 timerCtx
    func WithValue(parent Context, key, val interface{}) Context    //返回 valueCtx

可以實現面向對象編程中的多態(tài)用法

interface 只是定義一個或一組方法函數,但是這些方法只有函數簽名,沒有具體的實現,這個 C++ 中的虛函數非常類似。在 Go 里面,如果某個數據類型實現 interface 中定義的那些函數,則稱這些數據類型實現(implement)了這個接口 interface,這是我們常用的 OO 方式,如下是一個簡單的示例

    // 定義一個 SimpleLog 接口
    type SimpleLog interface {
        Print()
    }
    
    func TestFunc(x SimpleLog) {}
   
    // 定義一個 PrintImpl 結構,用來實現 SimpleLog 接口
    type PrintImpl struct {}
    // PrintImpl 對象實現了SimpleLog 接口的所有方法(本例中是 Print 方法),就說明實現了  SimpleLog 接口
    func (p *PrintImpl) Print() {
    
    }
    
    func main() {
        var p PrintImpl
        TestFunc(p)
    }

空接口可以接受任何類型的參數

空接口比較特殊,它不包含任何方法:interface{} ,在 Go 語言中,所有其它數據類型都實現了空接口,如下:

var v1 interface{} = 1
var v2 interface{} = "abc"
var v3 interface{} = struct{ X int }{1}

因此,當我們給 func 定義了一個 interface{} 類型的參數(也就是一個空接口)之后,那么這個參數可以接受任何類型,官方包中最典型的例子就是標準庫 fmt 包中的 Print 和 Fprint 系列的函數。

一個簡單的定義示例方法如下:

	Persist(context context.Context, msg interface{}) bool

msg 可以為任何類型,如 pb.MsgInfo or pb.GroupMsgInfo,定義方法的時候可以統(tǒng)一命名模塊,實現的時候,根據不同場景實現不同方法。

三、Go interface 的常見應用和實戰(zhàn)技巧

interface 接口賦值

可以將一個實現接口的對象實例賦值給接口,也可以將另外一個接口賦值給接口。

通過對象實例賦值

將一個對象實例賦值給一個接口之前,要保證該對象實現了接口的所有方法。在 Go 語言中,一個類只需要實現了接口要求的所有函數,我們就說這個類實現了該接口,這個是非侵入式接口的設計模式,非侵入式接口一個很重要的優(yōu)勢就是可以免去面向對象里面那套比較復雜的類的繼承體系。

在 Go 里面,面向對象的那套類的繼承體系就不需要關心了,定義接口的時候,我們只需關心這個接口應該提供哪些方法,當然,按照 Go 的原則,接口的功能要盡可能的保證職責單一。而對應接口的實現,也就是接口的調用方,我們只需要知道這個接口定義了哪些方法,然后我們實現這些方法就可以了,這個也無需提前規(guī)劃,調用方也無需關系是否有其他模塊定義過類似的接口或者實現,只關注自身就行。

考慮如下示例:

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 b1 LessAdder = &a  //OK
var b2 LessAdder = a   //not OK

b2 的賦值會報編譯錯誤,為什么呢?因為這個:The method set of any other named type T consists of all methods with receiver type T. The method set of the corresponding pointer type T is the set of all methods with receiver T or T (that is, it also contains the method set of T). 也就是說 *Integer 這個指針類型實現了接口 LessAdder 的所有方法,而 Integer 只實現了 Less 方法,所以不能賦值。

通過接口賦值

        var r io.Reader = new(os.File)
        var rw io.ReadWriter = r   //not ok
        var rw2 io.ReadWriter = new(os.File)
        var r2 io.Reader = rw2    //ok

因為 r 沒有 Write 方法,所以不能賦值給rw。

interface 接口嵌套

io package 中的一個接口:

// ReadWriter is the interface that groups the basic Read and Write methods.
type ReadWriter interface {
    Reader
    Writer
}

ReadWriter 接口嵌套了 io.Reader 和 io.Writer 兩個接口,實際上,它等同于下面的寫法:

type ReadWriter interface {
    Read(p []byte) (n int, err error) 
    Write(p []byte) (n int, err error)
}

注意,Go 語言中的接口不能遞歸嵌套,如下:

// illegal: Bad cannot embed itself
type Bad interface {
    Bad
}
// illegal: Bad1 cannot embed itself using Bad2
type Bad1 interface {
    Bad2
}
type Bad2 interface {
    Bad1
}

interface 強制類型轉換

ret, ok := interface.(type) 斷言

在 Go 語言中,可以通過 interface.(type) 的方式來對一個 interface 進行強制類型轉換,但是如果這個 interface 被轉換為一個不包含指定類型的類型,那么就會出現 panic 。因此,實戰(zhàn)應用中,我們通常都是通過 ret, ok := interface.(type) 這種斷言的方式來優(yōu)雅的進行轉換,這個方法中第一個返回值是對應類型的值,第二個返回值是類型是否正確,只有 ok = true 的情況下,才說明轉換成功,最重要的是,通過這樣的轉換方式可以避免直接轉換如果類型不對的情況下產生 panic。

如下是一個以 string 為類型的示例:

str, ok := value.(string)
if ok {
    fmt.Printf("string value is: %q\n", str)
} else {
    fmt.Printf("value is not a string\n")
}

如果類型斷言失敗,則str將依然存在,并且類型為字符串,不過其為零值,即一個空字符串。

switch x.(type) 斷言

查詢接口類型的方式為:

switch x.(type) {
    // cases :
}

示例如下:

var value interface{} // Value provided by caller.
switch str := value.(type) {
case string:
    return str //type of str is string
case int: 
    return int //type of str is int
}

語句switch中的value必須是接口類型,變量str的類型為轉換后的類型。

interface 與 nil 的比較

interface 與 nil 的比較是挺有意思的,例子是最好的說明,如下例子:

package main

import (
	"fmt"
	"reflect"
)

type State struct{}

func testnil1(a, b interface{}) bool {
	return a == b
}

func testnil2(a *State, b interface{}) bool {
	return a == b
}

func testnil3(a interface{}) bool {
	return a == nil
}

func testnil4(a *State) bool {
	return a == nil
}

func testnil5(a interface{}) bool {
	v := reflect.ValueOf(a)
	return !v.IsValid() || v.IsNil()
}

func main() {
	var a *State
	fmt.Println(testnil1(a, nil))
	fmt.Println(testnil2(a, nil))
	fmt.Println(testnil3(a))
	fmt.Println(testnil4(a))
	fmt.Println(testnil5(a))
}

運行后返回的結果如下

false
false
false
true
true

為什么是這個結果?

*因為一個 interface{} 類型的變量包含了2個指針,一個指針指向值的類型,另外一個指針指向實際的值。對一個 interface{} 類型的 nil 變量來說,它的兩個指針都是0;但是 var a State 傳進去后,指向的類型的指針不為0了,因為有類型了, 所以比較為 false。 interface 類型比較, 要是兩個指針都相等,才能相等。

到此這篇關于詳解Golang中interface接口的原理和使用技巧的文章就介紹到這了,更多相關Golang interface接口內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 詳解Go?中的時間處理

    詳解Go?中的時間處理

    這篇文章主要介紹了Go?中的時間處理,本文將介紹?time?庫中一些重要的函數和方法,希望能幫助到那些一遇到?Go?時間處理問題就需要百度的童鞋,需要的朋友可以參考下
    2022-07-07
  • 協(xié)同開發(fā)巧用gitignore中間件避免網絡請求攜帶登錄信息

    協(xié)同開發(fā)巧用gitignore中間件避免網絡請求攜帶登錄信息

    這篇文章主要為大家介紹了協(xié)同開發(fā)巧用gitignore中間件避免網絡請求攜帶登錄信息
    2022-06-06
  • 淺析golang如何在多線程中避免CPU指令重排

    淺析golang如何在多線程中避免CPU指令重排

    這篇文章主要為大家詳細介紹了golang在多線程中避免CPU指令重排的相關知識,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2024-03-03
  • 詳解Golang中使用map時的注意問題

    詳解Golang中使用map時的注意問題

    Golang中的map是一種數據結構,它允許你使用鍵值對的形式存儲和訪問數據,map在Go中是非排序的,提供了高效查找、插入和刪除元素的能力,特別是當鍵是不可變類型,本文給大家詳細介紹了Golang中使用map時的注意問題,需要的朋友可以參考下
    2024-06-06
  • Go語言實現冒泡排序、選擇排序、快速排序及插入排序的方法

    Go語言實現冒泡排序、選擇排序、快速排序及插入排序的方法

    這篇文章主要介紹了Go語言實現冒泡排序、選擇排序、快速排序及插入排序的方法,以實例形式詳細分析了幾種常見的排序技巧與實現方法,非常具有實用價值,需要的朋友可以參考下
    2015-02-02
  • Go語言操作數據庫及其常規(guī)操作的示例代碼

    Go語言操作數據庫及其常規(guī)操作的示例代碼

    這篇文章主要介紹了Go語言操作數據庫及其常規(guī)操作的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-04-04
  • go語言反射的基礎教程示例

    go語言反射的基礎教程示例

    這篇文章主要為大家介紹了go語言反射的基礎教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-08-08
  • Go語言實現Fibonacci數列的方法

    Go語言實現Fibonacci數列的方法

    這篇文章主要介紹了Go語言實現Fibonacci數列的方法,實例分析了使用遞歸和不使用遞歸兩種技巧,并對算法的效率進行了對比,需要的朋友可以參考下
    2015-02-02
  • Go channel發(fā)送方和接收方如何相互阻塞等待源碼解讀

    Go channel發(fā)送方和接收方如何相互阻塞等待源碼解讀

    這篇文章主要為大家介紹了Go channel發(fā)送方和接收方如何相互阻塞等待源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12
  • 一文徹底理解Golang閉包實現原理

    一文徹底理解Golang閉包實現原理

    閉包對于一個長期寫Java的開發(fā)者來說估計鮮有耳聞,光這名字感覺就有點"神秘莫測"。這篇文章的主要目的就是從編譯器的角度來分析閉包,徹底搞懂閉包的實現原理,需要的可以參考一下
    2022-10-10

最新評論