最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解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),只要是實際業(yè)務(wù)編程,并且想要寫出優(yōu)雅的代碼,那么必然要用上 interface,因此 interface 在 Go 語言里面處于非常核心的地位。

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

interface 的特性

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

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

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

關(guān)于接口賦值

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

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

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

interface 接口和 reflect 反射

在 Go 的實現(xiàn)里面,每個 interface 接口變量都有一個對應(yīng) pair,這個 pair 中記錄了接口的實際變量的類型和值(value, type),其中,value 是實際變量值,type 是實際變量的類型。任何一個 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 語言中實現(xiàn) reflect 反射的前提,理解了 pair,就更容易理解反射。反射就是用來檢測存儲在接口變量內(nèi)部(值value;類型concrete type) pair 對的一種機制。

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

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

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

在 C++ 等高級語言中使用泛型編程非常的簡單,但是 Go 在 1.18 版本之前,是不支持泛型的,而通過 Go 的接口,可以實現(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…),只要我們實現(xiàn)了這三個方法就可以使用 Sort 函數(shù),這樣就實現(xiàn)了“泛型編程”。

這種方式,我在閃聊項目里面也有實際應(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)
    }

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

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

例如我們常用的 context 包,就是這樣設(shè)計的,如果熟悉 Context 具體實現(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 的具體實現(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)部實現(xiàn)上下面三個函數(shù)返回的具體 struct (都實現(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

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

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

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

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

空接口比較特殊,它不包含任何方法:interface{} ,在 Go 語言中,所有其它數(shù)據(jù)類型都實現(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)一命名模塊,實現(xiàn)的時候,根據(jù)不同場景實現(xiàn)不同方法。

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

interface 接口賦值

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

通過對象實例賦值

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

在 Go 里面,面向?qū)ο蟮哪翘最惖睦^承體系就不需要關(guān)心了,定義接口的時候,我們只需關(guān)心這個接口應(yīng)該提供哪些方法,當(dāng)然,按照 Go 的原則,接口的功能要盡可能的保證職責(zé)單一。而對應(yīng)接口的實現(xiàn),也就是接口的調(diào)用方,我們只需要知道這個接口定義了哪些方法,然后我們實現(xiàn)這些方法就可以了,這個也無需提前規(guī)劃,調(diào)用方也無需關(guān)系是否有其他模塊定義過類似的接口或者實現(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 的賦值會報編譯錯誤,為什么呢?因為這個: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 這個指針類型實現(xiàn)了接口 LessAdder 的所有方法,而 Integer 只實現(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

因為 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 強制類型轉(zhuǎn)換

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

在 Go 語言中,可以通過 interface.(type) 的方式來對一個 interface 進(jìn)行強制類型轉(zhuǎn)換,但是如果這個 interface 被轉(zhuǎn)換為一個不包含指定類型的類型,那么就會出現(xiàn) panic 。因此,實戰(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))
}

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

false
false
false
true
true

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

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

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

相關(guān)文章

  • go語言int64整型轉(zhuǎn)字符串的實現(xiàn)

    go語言int64整型轉(zhuǎn)字符串的實現(xiàn)

    本文主要介紹了go語言int64整型轉(zhuǎn)字符串的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • Go語言error的設(shè)計理念及背景演化詳解

    Go語言error的設(shè)計理念及背景演化詳解

    這篇文章主要為大家介紹了Go語言error的設(shè)計理念及背景演化詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • golang中context的作用詳解

    golang中context的作用詳解

    這篇文章主要介紹了golang中context的作用,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-01-01
  • Golang實現(xiàn)優(yōu)雅的將struct轉(zhuǎn)換為map

    Golang實現(xiàn)優(yōu)雅的將struct轉(zhuǎn)換為map

    在項目實踐中,有時候我們需要將struct結(jié)構(gòu)體轉(zhuǎn)為map映射表,然后基于map做數(shù)據(jù)裁剪或操作。那么下面我來介紹下常用的兩種轉(zhuǎn)換方式,希望對大家有所幫助
    2023-01-01
  • Go語言中實現(xiàn)打印堆棧的errors包的用法詳解

    Go語言中實現(xiàn)打印堆棧的errors包的用法詳解

    因為Go語言提供的錯誤太簡單了,以至于簡單的我們無法更好的處理問題,所以誕生了很多對錯誤處理的庫,github.com/pkg/errors是比較簡潔的一樣,本文就來聊聊它的具體用法吧
    2023-07-07
  • Go語言中的方法、接口和嵌入類型詳解

    Go語言中的方法、接口和嵌入類型詳解

    這篇文章主要介紹了Go語言中的方法、接口和嵌入類型詳解,本文分別對它們做了詳細(xì)講解,需要的朋友可以參考下
    2014-10-10
  • 使用Golang實現(xiàn)WebSocket心跳機制

    使用Golang實現(xiàn)WebSocket心跳機制

    WebSocket是一種在客戶端和服務(wù)器之間實現(xiàn)全雙工通信的協(xié)議,它允許實時地傳輸數(shù)據(jù),并且比傳統(tǒng)的HTTP請求更加高效,在使用Golang構(gòu)建WebSocket應(yīng)用程序時,一個重要的考慮因素是如何實現(xiàn)心跳機制,所以本文將探討如何使用Golang實現(xiàn)WebSocket心跳
    2023-11-11
  • 為什么不建議在go項目中使用init()

    為什么不建議在go項目中使用init()

    這篇文章主要介紹了為什么不建議在go項目中使用init(),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • golang 實現(xiàn)菜單樹的生成方式

    golang 實現(xiàn)菜單樹的生成方式

    這篇文章主要介紹了golang 實現(xiàn)菜單樹的生成方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • Go語言中的if條件語句使用詳解

    Go語言中的if條件語句使用詳解

    這篇文章主要介紹了Go語言中的if條件語句的使用,包括if...else語句以及相關(guān)嵌套,需要的朋友可以參考下
    2015-10-10

最新評論

长丰县| 哈密市| 通海县| 宜兴市| 宿州市| 毕节市| 灵石县| 当涂县| 嘉禾县| 东阿县| 紫云| 紫云| 洞口县| 秭归县| 永济市| 九江县| 扎兰屯市| 融水| 宝兴县| 壶关县| 万州区| 垫江县| 宜兰市| 淮安市| 江陵县| 枣阳市| 神农架林区| 井冈山市| 家居| 辽中县| 玉山县| 巴青县| 巴里| 深圳市| 晋州市| 且末县| 松原市| 桃源县| 响水县| 洛浦县| 登封市|