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

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

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

一、Go interface 介紹

interface 在 Go 中的重要性說明

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

我們都知道,Go 語言和典型的面向?qū)ο蟮恼Z言不太一樣,Go 在語法上是不支持面向?qū)ο蟮念悺⒗^承等相關(guān)概念的。但是,并不代表 Go 里面不能實(shí)現(xiàn)面向?qū)ο蟮囊恍┬袨楸热缋^承、多態(tài),在 Go 里面,通過 interface 完全可以實(shí)現(xiàn)諸如 C++ 里面的繼承 和 多態(tài)的語法效果。

interface 的特性

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

關(guān)于接口的定義和簽名

  • 接口是一個或多個方法簽名的集合,接口只有方法聲明,沒有實(shí)現(xiàn),沒有數(shù)據(jù)字段,只要某個類型擁有該接口的所有方法簽名,那么就相當(dāng)于實(shí)現(xiàn)了該接口,無需顯示聲明了哪個接口,這稱為 Structural Typing。
  • interface 接口可以匿名嵌入其他接口中,或嵌入到 struct 結(jié)構(gòu)中
  • 接口可以支持匿名字段方法

關(guān)于接口賦值

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

關(guān)于接口嵌套,Go 里面支持接口嵌套,但是不支持遞歸嵌套

通過接口可以實(shí)現(xiàn)面向?qū)ο缶幊讨械亩鄳B(tài)的效果

interface 接口和 reflect 反射

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

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

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

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

可以實(shí)現(xiàn)泛型編程(雖然 Go 在 1.18 之后已經(jīng)支持泛型了)

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

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

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

    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)
    }

可以隱藏具體的實(shí)現(xiàn)

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

例如我們常用的 context 包,就是這樣設(shè)計(jì)的,如果熟悉 Context 具體實(shí)現(xiàn)的就會很容易理解。詳細(xì)代碼如下:

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

可以看到 WithCancel 函數(shù)返回的還是一個 Context interface,但是這個 interface 的具體實(shí)現(xiàn)是 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)
        }

盡管內(nèi)部實(shí)現(xiàn)上下面三個函數(shù)返回的具體 struct (都實(shí)現(xiàn)了 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

可以實(shí)現(xiàn)面向?qū)ο缶幊讨械亩鄳B(tài)用法

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

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

空接口可以接受任何類型的參數(shù)

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

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

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

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

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

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

三、Go interface 的常見應(yīng)用和實(shí)戰(zhàn)技巧

interface 接口賦值

可以將一個實(shí)現(xiàn)接口的對象實(shí)例賦值給接口,也可以將另外一個接口賦值給接口。

通過對象實(shí)例賦值

將一個對象實(shí)例賦值給一個接口之前,要保證該對象實(shí)現(xiàn)了接口的所有方法。在 Go 語言中,一個類只需要實(shí)現(xiàn)了接口要求的所有函數(shù),我們就說這個類實(shí)現(xiàn)了該接口,這個是非侵入式接口的設(shè)計(jì)模式,非侵入式接口一個很重要的優(yōu)勢就是可以免去面向?qū)ο罄锩婺翘妆容^復(fù)雜的類的繼承體系。

在 Go 里面,面向?qū)ο蟮哪翘最惖睦^承體系就不需要關(guān)心了,定義接口的時候,我們只需關(guān)心這個接口應(yīng)該提供哪些方法,當(dāng)然,按照 Go 的原則,接口的功能要盡可能的保證職責(zé)單一。而對應(yīng)接口的實(shí)現(xiàn),也就是接口的調(diào)用方,我們只需要知道這個接口定義了哪些方法,然后我們實(shí)現(xiàn)這些方法就可以了,這個也無需提前規(guī)劃,調(diào)用方也無需關(guān)系是否有其他模塊定義過類似的接口或者實(shí)現(xiàn),只關(guān)注自身就行。

考慮如下示例:

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 的賦值會報(bào)編譯錯誤,為什么呢?因?yàn)檫@個: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 這個指針類型實(shí)現(xiàn)了接口 LessAdder 的所有方法,而 Integer 只實(shí)現(xiàn)了 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

因?yàn)?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 兩個接口,實(shí)際上,它等同于下面的寫法:

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 強(qiáng)制類型轉(zhuǎn)換

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

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

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))
}

運(yùn)行后返回的結(jié)果如下

false
false
false
true
true

為什么是這個結(jié)果?

*因?yàn)橐粋€ interface{} 類型的變量包含了2個指針,一個指針指向值的類型,另外一個指針指向?qū)嶋H的值。對一個 interface{} 類型的 nil 變量來說,它的兩個指針都是0;但是 var a State 傳進(jìn)去后,指向的類型的指針不為0了,因?yàn)橛蓄愋土耍?所以比較為 false。 interface 類型比較, 要是兩個指針都相等,才能相等。

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

相關(guān)文章

  • 詳解Go?中的時間處理

    詳解Go?中的時間處理

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

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

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

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

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

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

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

    Go語言實(shí)現(xiàn)冒泡排序、選擇排序、快速排序及插入排序的方法

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

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

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

    go語言反射的基礎(chǔ)教程示例

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

    Go語言實(shí)現(xiàn)Fibonacci數(shù)列的方法

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

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

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

    一文徹底理解Golang閉包實(shí)現(xiàn)原理

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

最新評論