日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > windows >内容正文

windows

Go类型嵌入介绍和使用类型嵌入模拟实现“继承”

發(fā)布時(shí)間:2023/11/16 windows 100 coder
生活随笔 收集整理的這篇文章主要介紹了 Go类型嵌入介绍和使用类型嵌入模拟实现“继承” 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Go類(lèi)型嵌入介紹和使用類(lèi)型嵌入模擬實(shí)現(xiàn)“繼承”

目錄
  • Go類(lèi)型嵌入介紹和使用類(lèi)型嵌入模擬實(shí)現(xiàn)“繼承”
    • 一、獨(dú)立的自定義類(lèi)型
    • 二、繼承
    • 三、類(lèi)型嵌入
      • 3.1 什么是類(lèi)型嵌入
    • 四、接口類(lèi)型的類(lèi)型嵌入
      • 4.1 接口類(lèi)型的類(lèi)型嵌入介紹
      • 4.2 一個(gè)小案例
    • 五、結(jié)構(gòu)體類(lèi)型的類(lèi)型嵌入
      • 5.1 結(jié)構(gòu)體類(lèi)型的類(lèi)型嵌入介紹
      • 5.2 小案例
    • 六、“實(shí)現(xiàn)繼承”的原理
    • 七、類(lèi)型嵌入與方法集合
      • 7.1 結(jié)構(gòu)體類(lèi)型中嵌入接口類(lèi)型
      • 7.2 結(jié)構(gòu)體類(lèi)型中嵌入結(jié)構(gòu)體類(lèi)型
    • 八、defined 類(lèi)型與 alias 類(lèi)型是否可以實(shí)現(xiàn)方法集合的“繼承”?
      • 8.1 defined 類(lèi)型與 alias 類(lèi)型的方法集合
    • 九、小結(jié)

一、獨(dú)立的自定義類(lèi)型

什么是獨(dú)立的自定義類(lèi)型呢?就是這個(gè)類(lèi)型的所有方法都是自己顯式實(shí)現(xiàn)的。

我們舉個(gè)例子,自定義類(lèi)型 T 有兩個(gè)方法 M1M2,如果 T 是一個(gè)獨(dú)立的自定義類(lèi)型,那我們?cè)诼暶黝?lèi)型 T 的 Go 包源碼文件中一定可以找到其所有方法的實(shí)現(xiàn)代碼,比如:

func (T) M1() {...}
func (T) M2() {...}

難道還有某種自定義類(lèi)型的方法不是自己顯式實(shí)現(xiàn)的嗎?當(dāng)涉及到 Go 語(yǔ)言中的自定義類(lèi)型時(shí),有一種方法可以不需要顯式地實(shí)現(xiàn)方法,即:讓某個(gè)自定義類(lèi)型“繼承”其他類(lèi)型的方法實(shí)現(xiàn)。

二、繼承

Go 語(yǔ)言從設(shè)計(jì)伊始,就決定不支持經(jīng)典面向?qū)ο蟮木幊谭妒脚c語(yǔ)法元素,所以我們這里只是借用了“繼承”這個(gè)詞匯而已,說(shuō)是“繼承”,實(shí)則依舊是一種組合的思想

這種“繼承”是通過(guò) Go 語(yǔ)言的類(lèi)型嵌入(Type Embedding)來(lái)實(shí)現(xiàn)的。

三、類(lèi)型嵌入

3.1 什么是類(lèi)型嵌入

類(lèi)型嵌入指的就是在一個(gè)類(lèi)型的定義中嵌入了其他類(lèi)型。Go 語(yǔ)言支持兩種類(lèi)型嵌入,分別是接口類(lèi)型的類(lèi)型嵌入和結(jié)構(gòu)體類(lèi)型的類(lèi)型嵌入。

四、接口類(lèi)型的類(lèi)型嵌入

4.1 接口類(lèi)型的類(lèi)型嵌入介紹

接口類(lèi)型的類(lèi)型嵌入是指在一個(gè)接口類(lèi)型的定義中嵌入其他接口類(lèi)型,從而使接口類(lèi)型包含了嵌入接口中定義的方法。這允許一個(gè)接口類(lèi)型繼承另一個(gè)接口類(lèi)型的方法集,以擴(kuò)展其功能。

總結(jié)接口類(lèi)型的類(lèi)型嵌入的關(guān)鍵點(diǎn):

  1. 嵌入接口類(lèi)型:接口類(lèi)型可以嵌入其他接口類(lèi)型,將其方法集合并到當(dāng)前接口中。
  2. 繼承方法集:通過(guò)嵌入,接口類(lèi)型可以繼承嵌入接口中的方法,使得當(dāng)前接口也具有這些方法。
  3. 實(shí)現(xiàn)多態(tài):通過(guò)接口類(lèi)型的類(lèi)型嵌入,可以實(shí)現(xiàn)多態(tài),使不同類(lèi)型的對(duì)象可以被統(tǒng)一地處理,提高代碼的靈活性。

這種機(jī)制使得Go語(yǔ)言的接口更加靈活和可擴(kuò)展,允許將不同的接口組合在一起,以創(chuàng)建更復(fù)雜的接口,從而促進(jìn)了代碼的重用和可維護(hù)性。

4.2 一個(gè)小案例

接著,我們用一個(gè)案例,直觀地了解一下什么是接口類(lèi)型的類(lèi)型嵌入。我們知道,接口類(lèi)型聲明了由一個(gè)方法集合代表的接口,比如下面接口類(lèi)型 E

type E interface {
    M1()
    M2()
}

這個(gè)接口類(lèi)型 E 的方法集合,包含兩個(gè)方法,分別是 M1M2,它們組成了 E 這個(gè)接口類(lèi)型所代表的接口。如果某個(gè)類(lèi)型實(shí)現(xiàn)了方法 M1M2,我們就說(shuō)這個(gè)類(lèi)型實(shí)現(xiàn)了 E 所代表的接口。

此時(shí),我們?cè)俣x另外一個(gè)接口類(lèi)型 I,它的方法集合中包含了三個(gè)方法 M1M2M3,如下面代碼:

type I interface {
    M1()
    M2()
    M3()
}

我們看到接口類(lèi)型 I 方法集合中的 M1M2,與接口類(lèi)型 E 的方法集合中的方法完全相同。在這種情況下,我們可以用接口類(lèi)型 E 替代上面接口類(lèi)型 I 定義中 M1M2如下面代碼:

type I interface {
    E
    M3()
}

像這種在一個(gè)接口類(lèi)型(I)定義中,嵌入另外一個(gè)接口類(lèi)型(E)的方式,就是我們說(shuō)的接口類(lèi)型的類(lèi)型嵌入

而且,這個(gè)帶有類(lèi)型嵌入的接口類(lèi)型 I 的定義與上面那個(gè)包含 M1M2M3 的接口類(lèi)型 I 的定義,是等價(jià)的。因此,我們可以得到一個(gè)結(jié)論,這種接口類(lèi)型嵌入的語(yǔ)義就是新接口類(lèi)型(如接口類(lèi)型 I)將嵌入的接口類(lèi)型(如接口類(lèi)型 E)的方法集合,并入到自己的方法集合中。

其實(shí),使用類(lèi)型嵌入方式定義接口類(lèi)型也是 Go 組合設(shè)計(jì)哲學(xué)的一種體現(xiàn)

按 Go 語(yǔ)言慣例,Go 中的接口類(lèi)型中只包含少量方法,并且常常只是一個(gè)方法。通過(guò)在接口類(lèi)型中嵌入其他接口類(lèi)型可以實(shí)現(xiàn)接口的組合,這也是 Go 語(yǔ)言中基于已有接口類(lèi)型構(gòu)建新接口類(lèi)型的慣用法。

按 Go 語(yǔ)言慣例,Go 中的接口類(lèi)型中只包含少量方法,并且常常只是一個(gè)方法。通過(guò)在接口類(lèi)型中嵌入其他接口類(lèi)型可以實(shí)現(xiàn)接口的組合,這也是 Go 語(yǔ)言中基于已有接口類(lèi)型構(gòu)建新接口類(lèi)型的慣用法。

我們?cè)?Go 標(biāo)準(zhǔn)庫(kù)中可以看到很多這種組合方式的應(yīng)用,最常見(jiàn)的莫過(guò)于 io 包中一系列接口的定義了。比如,io 包的 ReadWriterReadWriteCloser 等接口類(lèi)型就是通過(guò)嵌入 ReaderWriterCloser 三個(gè)基本的接口類(lèi)型組合而成的。下面是僅包含單一方法的 ioReaderWriterCloser 的定義:

// $GOROOT/src/io/io.go

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

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

type Closer interface {
    Close() error
}

下面的 io 包的 ReadWriterReadWriteCloser 等接口類(lèi)型,通過(guò)嵌入上面基本接口類(lèi)型組合而形成:

type ReadWriter interface {
    Reader
    Writer
}

type ReadCloser interface {
    Reader
    Closer
}

type WriteCloser interface {
    Writer
    Closer
}

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

不過(guò),這種通過(guò)嵌入其他接口類(lèi)型來(lái)創(chuàng)建新接口類(lèi)型的方式,在 Go 1.14 版本之前是有約束的:如果新接口類(lèi)型嵌入了多個(gè)接口類(lèi)型,這些嵌入的接口類(lèi)型的方法集合不能有交集,同時(shí)嵌入的接口類(lèi)型的方法集合中的方法名字,也不能與新接口中的其他方法同名。比如我們用 Go 1.12.7 版本運(yùn)行下面例子,Go 編譯器就會(huì)報(bào)錯(cuò):

type Interface1 interface {
    M1()
}

type Interface2 interface {
    M1()
    M2()
}

type Interface3 interface {
    Interface1
    Interface2 // Error: duplicate method M1
}

type Interface4 interface {
    Interface2
    M2() // Error: duplicate method M2
}

func main() {
}

我們具體看一下例子中的兩個(gè)編譯報(bào)錯(cuò):第一個(gè)是因?yàn)?Interface3 中嵌入的兩個(gè)接口類(lèi)型 Interface1Interface2 的方法集合有交集,交集是方法 M1;第二個(gè)報(bào)錯(cuò)是因?yàn)?Interface4 類(lèi)型中的方法 M2 與嵌入的接口類(lèi)型 Interface2 的方法 M2 重名。

但自 Go 1.14 版本開(kāi)始,Go 語(yǔ)言去除了這些約束,我們使用 Go 最新版本運(yùn)行上面這個(gè)示例就不會(huì)得到編譯錯(cuò)誤了。

接口類(lèi)型的類(lèi)型嵌入比較簡(jiǎn)單,我們只要把握好它的語(yǔ)義,也就是“方法集合并入”就可以了。

五、結(jié)構(gòu)體類(lèi)型的類(lèi)型嵌入

5.1 結(jié)構(gòu)體類(lèi)型的類(lèi)型嵌入介紹

結(jié)構(gòu)體類(lèi)型的類(lèi)型嵌入是一種特殊的結(jié)構(gòu)體定義方式,其中結(jié)構(gòu)體的字段名可以直接使用類(lèi)型名、類(lèi)型的指針類(lèi)型名或接口類(lèi)型名,代表字段的名字和類(lèi)型。以下是結(jié)構(gòu)體類(lèi)型的類(lèi)型嵌入的關(guān)鍵點(diǎn):

  1. 字段名和類(lèi)型合二為一:在結(jié)構(gòu)體類(lèi)型的類(lèi)型嵌入中,字段名和類(lèi)型名合并成一個(gè)標(biāo)識(shí)符,既代表了字段的名字又代表了字段的類(lèi)型。這使得字段名與類(lèi)型名保持一致,簡(jiǎn)化了結(jié)構(gòu)體定義。
  2. 嵌入字段:這種方式被稱(chēng)為嵌入字段(Embedded Field),其中嵌入字段的類(lèi)型可以是自定義類(lèi)型、結(jié)構(gòu)體類(lèi)型的指針類(lèi)型,或接口類(lèi)型。
  3. 訪問(wèn)嵌入字段:可以通過(guò)結(jié)構(gòu)體變量來(lái)訪問(wèn)嵌入字段的字段和方法,無(wú)需使用字段名,因?yàn)樽侄蚊呀?jīng)隱含在類(lèi)型中。
  4. 字段名與類(lèi)型名一致:嵌入字段的字段名與類(lèi)型名一致,這種一致性使得代碼更加清晰和直觀。
  5. 類(lèi)型組合:通過(guò)嵌入字段,可以將不同類(lèi)型的功能組合在一個(gè)結(jié)構(gòu)體中,形成更復(fù)雜的數(shù)據(jù)結(jié)構(gòu),提高代碼的可維護(hù)性和擴(kuò)展性。

5.2 小案例

通常,結(jié)構(gòu)體都是類(lèi)似下面這樣的:

type S struct {
    A int
    b string
    c T
    p *P
    _ [10]int8
    F func()
}

結(jié)構(gòu)體類(lèi)型 S 中的每個(gè)字段(field)都有唯一的名字與對(duì)應(yīng)的類(lèi)型,即便是使用空標(biāo)識(shí)符占位的字段,它的類(lèi)型也是明確的,但這還不是 Go 結(jié)構(gòu)體類(lèi)型的“完全體”。Go 結(jié)構(gòu)體類(lèi)型定義還有另外一種形式,那就是帶有嵌入字段(Embedded Field)的結(jié)構(gòu)體定義。我們看下面這個(gè)例子:

type T1 int
type t2 struct{
    n int
    m int
}

type I interface {
    M1()
}

type S1 struct {
    T1
    *t2
    I            
    a int
    b string
}

我們看到,結(jié)構(gòu)體 S1 定義中有三個(gè)“非常規(guī)形式”的標(biāo)識(shí)符,分別是 T1t2I,這三個(gè)標(biāo)識(shí)符究竟代表的是什么呢?是字段名還是字段的類(lèi)型呢?這里我直接告訴你答案:它們既代表字段的名字,也代表字段的類(lèi)型。我們分別以這三個(gè)標(biāo)識(shí)符為例,說(shuō)明一下它們的具體含義:

  • 標(biāo)識(shí)符 T1 表示字段名為 T1,它的類(lèi)型為自定義類(lèi)型 T1;
  • 標(biāo)識(shí)符 t2 表示字段名為 t2,它的類(lèi)型為自定義結(jié)構(gòu)體類(lèi)型 t2 的指針類(lèi)型;
  • 標(biāo)識(shí)符 I 表示字段名為 I,它的類(lèi)型為接口類(lèi)型 I。

這種以某個(gè)類(lèi)型名、類(lèi)型的指針類(lèi)型名或接口類(lèi)型名,直接作為結(jié)構(gòu)體字段的方式就叫做結(jié)構(gòu)體的類(lèi)型嵌入,這些字段也被叫做嵌入字段(Embedded Field)。

那么,嵌入字段怎么用呢?它跟普通結(jié)構(gòu)體字段有啥不同呢?我們結(jié)合具體的例子,簡(jiǎn)單說(shuō)一下嵌入字段的用法:

type MyInt int

func (n *MyInt) Add(m int) {
    *n = *n + MyInt(m)
}

type t struct {
    a int
    b int
}

type S struct {
    *MyInt
    t
    io.Reader
    s string
    n int
}

func main() {
    m := MyInt(17)
    r := strings.NewReader("hello, go")
    s := S{
        MyInt: &m,
        t: t{
            a: 1,
            b: 2,
        },
        Reader: r,
        s:      "demo",
    }

    var sl = make([]byte, len("hello, go"))
    s.Reader.Read(sl)
    fmt.Println(string(sl)) // hello, go
    s.MyInt.Add(5)
    fmt.Println(*(s.MyInt)) // 22
}

在分析這段代碼之前,我們要先明確一點(diǎn),那就是嵌入字段的可見(jiàn)性與嵌入字段的類(lèi)型的可見(jiàn)性是一致的。如果嵌入類(lèi)型的名字是首字母大寫(xiě)的,那么也就說(shuō)明這個(gè)嵌入字段是可導(dǎo)出的。

現(xiàn)在我們來(lái)看這個(gè)例子。

首先,這個(gè)例子中的結(jié)構(gòu)體類(lèi)型 S 使用了類(lèi)型嵌入方式進(jìn)行定義,它有三個(gè)嵌入字段 MyInttReader。這里,你可能會(huì)問(wèn),為什么第三個(gè)嵌入字段的名字為 Reader 而不是 io.Reader?這是因?yàn)椋珿o 語(yǔ)言規(guī)定如果結(jié)構(gòu)體使用從其他包導(dǎo)入的類(lèi)型作為嵌入字段,比如 pkg.T,那么這個(gè)嵌入字段的字段名就是 T,代表的類(lèi)型為 pkg.T

接下來(lái),我們?cè)賮?lái)看結(jié)構(gòu)體類(lèi)型 S 的變量的初始化。我們使用 field:value 方式對(duì) S 類(lèi)型的變量 s 的各個(gè)字段進(jìn)行初始化。和普通的字段一樣,初始化嵌入字段時(shí),我們可以直接用嵌入字段名作為 field

而且,通過(guò)變量 s 使用這些嵌入字段時(shí),我們也可以像普通字段那樣直接用 變量s + 字段選擇符 + 嵌入字段的名字,比如 s.Reader。我們還可以通過(guò)這種方式調(diào)用嵌入字段的方法,比如 s.Reader.Reads.MyInt.Add

這樣看起來(lái),嵌入字段的用法和普通字段沒(méi)啥不同呀?也不完全是,Go 還是對(duì)嵌入字段有一些約束的。比如,和 Go 方法的 receiver 的基類(lèi)型一樣,嵌入字段類(lèi)型的底層類(lèi)型不能為指針類(lèi)型。而且,嵌入字段的名字在結(jié)構(gòu)體定義也必須是唯一的,這也意味這如果兩個(gè)類(lèi)型的名字相同,它們無(wú)法同時(shí)作為嵌入字段放到同一個(gè)結(jié)構(gòu)體定義中。不過(guò),這些約束你了解一下就可以了,一旦違反,Go 編譯器會(huì)提示你的。

六、“實(shí)現(xiàn)繼承”的原理

將上面例子代碼做一下細(xì)微改動(dòng),我這里只列了變化部分的代碼:

var sl = make([]byte, len("hello, go"))
s.Read(sl) 
fmt.Println(string(sl))
s.Add(5) 
fmt.Println(*(s.MyInt))

這段代碼中,類(lèi)型 S 也沒(méi)有定義 Read 方法和 Add 方法,但是這段程序不但沒(méi)有引發(fā)編譯器報(bào)錯(cuò),還可以正常運(yùn)行并輸出與前面例子相同的結(jié)果!

這段代碼似乎在告訴我們:Read 方法與 Add 方法就是類(lèi)型 S 方法集合中的方法。但是,這里類(lèi)型 S 明明沒(méi)有顯式實(shí)現(xiàn)這兩個(gè)方法呀,它是從哪里得到這兩個(gè)方法的實(shí)現(xiàn)的呢?

其實(shí),這兩個(gè)方法就來(lái)自結(jié)構(gòu)體類(lèi)型 S 的兩個(gè)嵌入字段 ReaderMyInt。結(jié)構(gòu)體類(lèi)型 S “繼承”了 Reader 字段的方法 Read 的實(shí)現(xiàn),也“繼承”了 *MyIntAdd 方法的實(shí)現(xiàn)。注意,我這里的“繼承”用了引號(hào),說(shuō)明這并不是真正的繼承,它只是 Go 語(yǔ)言的一種“障眼法”。

這種“障眼法”的工作機(jī)制是這樣的,當(dāng)我們通過(guò)結(jié)構(gòu)體類(lèi)型 S 的變量 s 調(diào)用 Read 方法時(shí),Go 發(fā)現(xiàn)結(jié)構(gòu)體類(lèi)型 S 自身并沒(méi)有定義 Read 方法,于是 Go 會(huì)查看 S 的嵌入字段對(duì)應(yīng)的類(lèi)型是否定義了 Read 方法。這個(gè)時(shí)候,Reader 字段就被找了出來(lái),之后 s.Read 的調(diào)用就被轉(zhuǎn)換為 s.Reader.Read 調(diào)用。

這樣一來(lái),嵌入字段 ReaderRead 方法就被提升為 S 的方法,放入了類(lèi)型 S 的方法集合。同理 *MyIntAdd 方法也被提升為 S 的方法而放入 S 的方法集合。從外部來(lái)看,這種嵌入字段的方法的提升就給了我們一種結(jié)構(gòu)體類(lèi)型 S“繼承”了 io.Reader 類(lèi)型 Read 方法的實(shí)現(xiàn),以及 *MyInt 類(lèi)型 Add 方法的實(shí)現(xiàn)的錯(cuò)覺(jué)。

到這里,我們就清楚了,嵌入字段的使用的確可以幫我們?cè)?Go 中實(shí)現(xiàn)方法的“繼承”。

在文章開(kāi)頭,類(lèi)型嵌入這種看似“繼承”的機(jī)制,實(shí)際上是一種組合的思想。更具體點(diǎn),它是一種組合中的代理(delegate)模式,如下圖所示:

我們看到,S 只是一個(gè)代理(delegate),對(duì)外它提供了它可以代理的所有方法,如例子中的 ReadAdd 方法。當(dāng)外界發(fā)起對(duì) SRead 方法的調(diào)用后,S 將該調(diào)用委派給它內(nèi)部的 Reader 實(shí)例來(lái)實(shí)際執(zhí)行 Read 方法。

七、類(lèi)型嵌入與方法集合

在前面,接口類(lèi)型的類(lèi)型嵌入時(shí)我們提到接口類(lèi)型的類(lèi)型嵌入的本質(zhì),就是嵌入類(lèi)型的方法集合并入到新接口類(lèi)型的方法集合中,并且,接口類(lèi)型只能嵌入接口類(lèi)型。而結(jié)構(gòu)體類(lèi)型對(duì)嵌入類(lèi)型的要求就比較寬泛了,可以是任意自定義類(lèi)型或接口類(lèi)型。

下面我們就分別看看,在這兩種情況下,結(jié)構(gòu)體類(lèi)型的方法集合會(huì)有怎樣的變化。我們依舊借助上一講中的 dumpMethodSet 函數(shù)來(lái)輸出各個(gè)類(lèi)型的方法集合,這里,我就不在例子中重復(fù)列出 dumpMethodSet 的代碼了。

7.1 結(jié)構(gòu)體類(lèi)型中嵌入接口類(lèi)型

在結(jié)構(gòu)體類(lèi)型中嵌入接口類(lèi)型后,結(jié)構(gòu)體類(lèi)型的方法集合會(huì)發(fā)生什么變化呢?我們通過(guò)下面這個(gè)例子來(lái)看一下:

type I interface {
    M1()
    M2()
}

type T struct {
    I
}

func (T) M3() {}

func main() {
    var t T
    var p *T
    dumpMethodSet(t)
    dumpMethodSet(p)
}

運(yùn)行這個(gè)示例,我們會(huì)得到以下結(jié)果:

main.T's method set:
- M1
- M2
- M3

*main.T's method set:
- M1
- M2
- M3

我們可以看到,原本結(jié)構(gòu)體類(lèi)型 T 只帶有一個(gè)方法 M3,但在嵌入接口類(lèi)型 I 后,結(jié)構(gòu)體類(lèi)型 T 的方法集合中又并入了接口類(lèi)型 I 的方法集合。并且,由于 *T 類(lèi)型方法集合包括 T 類(lèi)型的方法集合,因此無(wú)論是類(lèi)型 T 還是類(lèi)型 *T,它們的方法集合都包含 M1M2M3。于是我們可以得出一個(gè)結(jié)論:結(jié)構(gòu)體類(lèi)型的方法集合,包含嵌入的接口類(lèi)型的方法集合。

不過(guò)有一種情況,你要注意一下,那就是當(dāng)結(jié)構(gòu)體嵌入的多個(gè)接口類(lèi)型的方法集合存在交集時(shí),你要小心編譯器可能會(huì)出現(xiàn)的錯(cuò)誤提示。

雖然Go 1.14 版本解決了嵌入接口類(lèi)型的方法集合有交集的情況,但那僅限于接口類(lèi)型中嵌入接口類(lèi)型,這里我們說(shuō)的是在結(jié)構(gòu)體類(lèi)型中嵌入方法集合有交集的接口類(lèi)型。

根據(jù)我們前面講的,嵌入了其他類(lèi)型的結(jié)構(gòu)體類(lèi)型本身是一個(gè)代理,在調(diào)用其實(shí)例所代理的方法時(shí),Go 會(huì)首先查看結(jié)構(gòu)體自身是否實(shí)現(xiàn)了該方法。

如果實(shí)現(xiàn)了,Go 就會(huì)優(yōu)先使用結(jié)構(gòu)體自己實(shí)現(xiàn)的方法。如果沒(méi)有實(shí)現(xiàn),那么 Go 就會(huì)查找結(jié)構(gòu)體中的嵌入字段的方法集合中,是否包含了這個(gè)方法。如果多個(gè)嵌入字段的方法集合中都包含這個(gè)方法,那么我們就說(shuō)方法集合存在交集。這個(gè)時(shí)候,Go 編譯器就會(huì)因無(wú)法確定究竟使用哪個(gè)方法而報(bào)錯(cuò),下面的這個(gè)例子就演示了這種情況:

  type E1 interface {
      M1()
      M2()
      M3()
  }
  
  type E2 interface {
     M1()
     M2()
     M4()
 }
 
 type T struct {
     E1
     E2
 }
 
 func main() {
     t := T{}
     t.M1()
     t.M2()
 }

運(yùn)行這個(gè)例子,我們會(huì)得到:

main.go:22:3: ambiguous selector t.M1
main.go:23:3: ambiguous selector t.M2

我們看到,Go 編譯器給出了錯(cuò)誤提示,表示在調(diào)用 t.M1t.M2 時(shí),編譯器都出現(xiàn)了分歧。在這個(gè)例子中,結(jié)構(gòu)體類(lèi)型 T 嵌入的兩個(gè)接口類(lèi)型 E1E2 的方法集合存在交集,都包含 M1M2,而結(jié)構(gòu)體類(lèi)型 T 自身呢,又沒(méi)有實(shí)現(xiàn) M1M2,所以編譯器會(huì)因無(wú)法做出選擇而報(bào)錯(cuò)。

那怎么解決這個(gè)問(wèn)題呢?其實(shí)有兩種解決方案。一是,我們可以消除 E1 和 E2 方法集合存在交集的情況。二是為 T 增加 M1 和 M2 方法的實(shí)現(xiàn),這樣的話,編譯器便會(huì)直接選擇 T 自己實(shí)現(xiàn)的 M1 和 M2,不會(huì)陷入兩難境地。比如,下面的例子演示的就是 T 增加了 M1 和 M2 方法實(shí)現(xiàn)的情況:

... ...
type T struct {
    E1
    E2
}

func (T) M1() { println("T's M1") }
func (T) M2() { println("T's M2") }

func main() {
    t := T{}
    t.M1() // T's M1
    t.M2() // T's M2
}

結(jié)構(gòu)體類(lèi)型嵌入接口類(lèi)型在日常編碼中有一個(gè)妙用,就是可以簡(jiǎn)化單元測(cè)試的編寫(xiě)。由于嵌入某接口類(lèi)型的結(jié)構(gòu)體類(lèi)型的方法集合包含了這個(gè)接口類(lèi)型的方法集合,這就意味著,這個(gè)結(jié)構(gòu)體類(lèi)型也是它嵌入的接口類(lèi)型的一個(gè)實(shí)現(xiàn)。即便結(jié)構(gòu)體類(lèi)型自身并沒(méi)有實(shí)現(xiàn)這個(gè)接口類(lèi)型的任意一個(gè)方法,也沒(méi)有關(guān)系。我們來(lái)看一個(gè)直觀的例子:

package employee
  
type Result struct {
    Count int
}

func (r Result) Int() int { return r.Count }

type Rows []struct{}

type Stmt interface {
    Close() error
    NumInput() int
    Exec(stmt string, args ...string) (Result, error)
    Query(args []string) (Rows, error)
}

// 返回男性員工總數(shù)
func MaleCount(s Stmt) (int, error) {
    result, err := s.Exec("select count(*) from employee_tab where gender=?", "1")
    if err != nil {
        return 0, err
    }

    return result.Int(), nil
}

在這個(gè)例子中,我們有一個(gè) employee 包,這個(gè)包中的方法 MaleCount,通過(guò)傳入的 Stmt 接口的實(shí)現(xiàn)從數(shù)據(jù)庫(kù)獲取男性員工的數(shù)量。

現(xiàn)在我們的任務(wù)是要對(duì) MaleCount 方法編寫(xiě)單元測(cè)試代碼。對(duì)于這種依賴(lài)外部數(shù)據(jù)庫(kù)操作的方法,我們的慣例是使用“偽對(duì)象(fake object)”來(lái)冒充真實(shí)的 Stmt 接口實(shí)現(xiàn)。

不過(guò)現(xiàn)在有一個(gè)問(wèn)題,那就是 Stmt 接口類(lèi)型的方法集合中有四個(gè)方法,而 MaleCount 函數(shù)只使用了 Stmt 接口的一個(gè)方法 Exec。如果我們針對(duì)每個(gè)測(cè)試用例所用的偽對(duì)象都實(shí)現(xiàn)這四個(gè)方法,那么這個(gè)工作量有些大。

那么這個(gè)時(shí)候,我們?cè)鯓涌焖俳螌?duì)象呢?結(jié)構(gòu)體類(lèi)型嵌入接口類(lèi)型便可以幫助我們,下面是我們的解決方案:

package employee
  
import "testing"

type fakeStmtForMaleCount struct {
    Stmt
}

func (fakeStmtForMaleCount) Exec(stmt string, args ...string) (Result, error) {
    return Result{Count: 5}, nil
}

func TestEmployeeMaleCount(t *testing.T) {
    f := fakeStmtForMaleCount{}
    c, _ := MaleCount(f)
    if c != 5 {
        t.Errorf("want: %d, actual: %d", 5, c)
        return
    }
}

我們?yōu)?TestEmployeeMaleCount 測(cè)試用例建立了一個(gè) fakeStmtForMaleCount 的偽對(duì)象類(lèi)型,然后在這個(gè)類(lèi)型中嵌入了 Stmt 接口類(lèi)型。這樣 fakeStmtForMaleCount 就實(shí)現(xiàn)了 Stmt 接口,我們也實(shí)現(xiàn)了快速建立偽對(duì)象的目的。接下來(lái)我們只需要為 fakeStmtForMaleCount 實(shí)現(xiàn) MaleCount 所需的 Exec 方法,就可以滿足這個(gè)測(cè)試的要求了。

7.2 結(jié)構(gòu)體類(lèi)型中嵌入結(jié)構(gòu)體類(lèi)型

在前面結(jié)構(gòu)體類(lèi)型中嵌入結(jié)構(gòu)體類(lèi)型,為 Gopher 們提供了一種“實(shí)現(xiàn)繼承”的手段,外部的結(jié)構(gòu)體類(lèi)型 T 可以“繼承”嵌入的結(jié)構(gòu)體類(lèi)型的所有方法的實(shí)現(xiàn)。并且,無(wú)論是 T 類(lèi)型的變量實(shí)例還是 *T 類(lèi)型變量實(shí)例,都可以調(diào)用所有“繼承”的方法。但這種情況下,帶有嵌入類(lèi)型的新類(lèi)型究竟“繼承”了哪些方法,我們還要通過(guò)下面這個(gè)具體的示例來(lái)看一下。

type T1 struct{}

func (T1) T1M1()   { println("T1's M1") }
func (*T1) PT1M2() { println("PT1's M2") }

type T2 struct{}

func (T2) T2M1()   { println("T2's M1") }
func (*T2) PT2M2() { println("PT2's M2") }

type T struct {
    T1
    *T2
}

func main() {
    t := T{
        T1: T1{},
        T2: &T2{},
    }

    dumpMethodSet(t)
    dumpMethodSet(&t)
}

在這個(gè)例子中,結(jié)構(gòu)體類(lèi)型 T 有兩個(gè)嵌入字段,分別是 T1*T2,根據(jù)上一講中我們對(duì)結(jié)構(gòu)體的方法集合的講解,我們知道 T1*T1T2*T2 的方法集合是不同的:

  • T1 的方法集合包含:T1M1
  • *T1 的方法集合包含:T1M1PT1M2
  • T2 的方法集合包含:T2M1
  • *T2 的方法集合包含:T2M1PT2M2

它們作為嵌入字段嵌入到 T 中后,對(duì) T*T 的方法集合的影響也是不同的。我們運(yùn)行一下這個(gè)示例,看一下輸出結(jié)果:

main.T's method set:
- PT2M2
- T1M1
- T2M1

*main.T's method set:
- PT1M2
- PT2M2
- T1M1
- T2M1

通過(guò)輸出結(jié)果,我們看到了 T*T 類(lèi)型的方法集合果然有差別的:

  • 類(lèi)型 T 的方法集合 = T1 的方法集合 + *T2 的方法集合
  • 類(lèi)型 *T 的方法集合 = *T1 的方法集合 + *T2 的方法集合

這里,我們尤其要注意 *T 類(lèi)型的方法集合,它包含的可不是 T1 類(lèi)型的方法集合,而是 *T1 類(lèi)型的方法集合。這和結(jié)構(gòu)體指針類(lèi)型的方法集合包含結(jié)構(gòu)體類(lèi)型方法集合,是一個(gè)道理。

到這里,基于類(lèi)型嵌入“繼承”方法實(shí)現(xiàn)的原理,我們基本都清楚了。但不知道你會(huì)不會(huì)還有一點(diǎn)疑惑:只有通過(guò)類(lèi)型嵌入才能實(shí)現(xiàn)方法“繼承”嗎?如果我使用類(lèi)型聲明語(yǔ)法基于一個(gè)已有類(lèi)型 T 定義一個(gè)新類(lèi)型 NT,那么 NT 是不是可以直接繼承 T 的所有方法呢?

八、defined 類(lèi)型與 alias 類(lèi)型是否可以實(shí)現(xiàn)方法集合的“繼承”?

8.1 defined 類(lèi)型與 alias 類(lèi)型的方法集合

Go 語(yǔ)言中,凡通過(guò)類(lèi)型聲明語(yǔ)法聲明的類(lèi)型都被稱(chēng)為 defined 類(lèi)型,下面是一些 defined 類(lèi)型的聲明的例子:

type I interface {
    M1()
    M2()
}
type T int
type NT T // 基于已存在的類(lèi)型T創(chuàng)建新的defined類(lèi)型NT
type NI I // 基于已存在的接口類(lèi)型I創(chuàng)建新defined接口類(lèi)型NI

新定義的 defined 類(lèi)型與原 defined 類(lèi)型是不同的類(lèi)型,那么它們的方法集合上又會(huì)有什么關(guān)系呢?新類(lèi)型是否“繼承”原 defined 類(lèi)型的方法集合呢?

這個(gè)問(wèn)題,我們也要分情況來(lái)看。

對(duì)于那些基于接口類(lèi)型創(chuàng)建的 defined 的接口類(lèi)型,它們的方法集合與原接口類(lèi)型的方法集合是一致的。但對(duì)于基于非接口類(lèi)型的 defined 類(lèi)型創(chuàng)建的非接口類(lèi)型,我們通過(guò)下面例子來(lái)看一下:

package main

type T struct{}

func (T) M1()  {}
func (*T) M2() {}

type T1 T

func main() {
  var t T
  var pt *T
  var t1 T1
  var pt1 *T1

  dumpMethodSet(t)
  dumpMethodSet(t1)

  dumpMethodSet(pt)
  dumpMethodSet(pt1)
}

在這個(gè)例子中,我們基于一個(gè) defined 的非接口類(lèi)型 T 創(chuàng)建了新 defined 類(lèi)型 T1,并且分別輸出 T1*T1 的方法集合來(lái)確認(rèn)它們是否“繼承”了 T 的方法集合。

運(yùn)行這個(gè)示例程序,我們得到如下結(jié)果:

main.T's method set:
- M1

main.T1's method set is empty!

*main.T's method set:
- M1
- M2

*main.T1's method set is empty!

從輸出結(jié)果上看,新類(lèi)型 T1 并沒(méi)有“繼承”原 defined 類(lèi)型 T 的任何一個(gè)方法。從邏輯上來(lái)說(shuō),這也符合 T1T 是兩個(gè)不同類(lèi)型的語(yǔ)義。

基于自定義非接口類(lèi)型的 defined 類(lèi)型的方法集合為空的事實(shí),也決定了即便原類(lèi)型實(shí)現(xiàn)了某些接口,基于其創(chuàng)建的 defined 類(lèi)型也沒(méi)有“繼承”這一隱式關(guān)聯(lián)。也就是說(shuō),新 defined 類(lèi)型要想實(shí)現(xiàn)那些接口,仍然需要重新實(shí)現(xiàn)接口的所有方法。

那么,基于類(lèi)型別名(type alias)定義的新類(lèi)型有沒(méi)有“繼承”原類(lèi)型的方法集合呢?我們還是來(lái)看一個(gè)例子:

type T struct{}

func (T) M1()  {}
func (*T) M2() {}

type T1 = T

func main() {
    var t T
    var pt *T
    var t1 T1
    var pt1 *T1

    dumpMethodSet(t)
    dumpMethodSet(t1)

    dumpMethodSet(pt)
    dumpMethodSet(pt1)
}

這個(gè)例子改自之前那個(gè)例子,我只是將 T1 的定義方式由類(lèi)型聲明改成了類(lèi)型別名,我們看一下這個(gè)例子的輸出結(jié)果:

main.T's method set:
- M1

main.T's method set:
- M1

*main.T's method set:
- M1
- M2

*main.T's method set:
- M1
- M2

通過(guò)這個(gè)輸出結(jié)果,我們看到,我們的 dumpMethodSet 函數(shù)甚至都無(wú)法識(shí)別出“類(lèi)型別名”,無(wú)論類(lèi)型別名還是原類(lèi)型,輸出的都是原類(lèi)型的方法集合。

由此我們可以得到一個(gè)結(jié)論:無(wú)論原類(lèi)型是接口類(lèi)型還是非接口類(lèi)型,類(lèi)型別名都與原類(lèi)型擁有完全相同的方法集合。

九、小結(jié)

類(lèi)型嵌入分為兩種,一種是接口類(lèi)型的類(lèi)型嵌入,對(duì)于接口類(lèi)型的類(lèi)型嵌入我們只要把握好其語(yǔ)義“方法集合并入”就可以了。另外一種是結(jié)構(gòu)體類(lèi)型的類(lèi)型嵌入。通過(guò)在結(jié)構(gòu)體定義中的嵌入字段,我們可以實(shí)現(xiàn)對(duì)嵌入類(lèi)型的方法集合的“繼承”。

但這種“繼承”并非經(jīng)典面向?qū)ο蠓妒街械哪莻€(gè)繼承,Go 中的“繼承”實(shí)際是一種組合,更具體點(diǎn)是組合思想下代理(delegate)模式的運(yùn)用,也就是新類(lèi)型代理了其嵌入類(lèi)型的所有方法。當(dāng)外界調(diào)用新類(lèi)型的方法時(shí),Go 編譯器會(huì)首先查找新類(lèi)型是否實(shí)現(xiàn)了這個(gè)方法,如果沒(méi)有,就會(huì)將調(diào)用委派給其內(nèi)部實(shí)現(xiàn)了這個(gè)方法的嵌入類(lèi)型的實(shí)例去執(zhí)行,你一定要理解這個(gè)原理。

此外,你還要牢記類(lèi)型嵌入對(duì)新類(lèi)型的方法集合的影響,包括:

  • 結(jié)構(gòu)體類(lèi)型的方法集合包含嵌入的接口類(lèi)型的方法集合;

  • 當(dāng)結(jié)構(gòu)體類(lèi)型 T 包含嵌入字段 E 時(shí),*T 的方法集合不僅包含類(lèi)型 E 的方法集合,還要包含類(lèi)型 *E 的方法集合。

最后,基于非接口類(lèi)型的 defined 類(lèi)型創(chuàng)建的新 defined 類(lèi)型不會(huì)繼承原類(lèi)型的方法集合,而通過(guò)類(lèi)型別名定義的新類(lèi)型則和原類(lèi)型擁有相同的方法集合。

總結(jié)

以上是生活随笔為你收集整理的Go类型嵌入介绍和使用类型嵌入模拟实现“继承”的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 欧美激情网 | 亚洲人成电影在线 | 成人av免费在线看 | 人妻激情文学 | 少妇视频一区 | 四虎成人永久免费视频 | 国产在线综合视频 | 国产精品va在线观看无码 | 亚洲一区二区三区在线视频 | 国产精品久久久精品三级 | 欧美成人精品一区二区三区在线看 | 在线欧美激情 | 午夜精品无码一区二区三区 | 精品伦精品一区二区三区视频密桃 | 男女激情免费网站 | 姑娘第5集高清在线观看 | 高清一区二区三区四区五区 | 麻豆视频免费在线观看 | 岛国精品一区二区 | 中文字幕在线免费视频 | 国产乱色精品成人免费视频 | 久久国产精品精品国产色婷婷 | 激情网久久 | 夜夜嗨av一区二区三区免费区 | 狠狠干狠狠干狠狠干 | 久久午夜夜伦鲁鲁片无码免费 | 欧日韩在线视频 | 打屁股调教视频 | 制服丝袜先锋影音 | 毛片毛片毛片毛片毛片毛片毛片毛片毛片毛片 | 青青艹在线视频 | 成人自拍视频在线 | 日本裸体视频 | 久久中文字幕一区 | 中国黄色网页 | 国产喷白浆一区二区三区 | 久久男| 丰满人妻一区二区三区免费视频棣 | 亚洲视频欧美 | 欧美性大战久久久久xxx | 国产清纯白嫩初高中在线观看性色 | 依人99| 奇米综合 | 欧美天堂在线视频 | 国产视频一区二区三区在线播放 | 色偷偷一区二区三区 | 97免费视频观看 | 裸体女视频 | 亚洲国产成人精品91久久久 | 亚洲少妇视频 | 亚洲综合五月天婷婷丁香 | 国产制服91一区二区三区制服 | 欧美精品videos极品 | 日韩狠狠 | 亚洲欧美日本在线观看 | 激情综合婷婷 | 3p视频在线 | 成人免费观看视频网站 | 中文字幕成人av | 性自由色xxxx免费视频 | 国内精品偷拍视频 | 96国产在线 | 国产真实老熟女无套内射 | 人人干夜夜操 | 精品免费国产一区二区三区四区 | 亚洲精品乱码久久久久久蜜桃动漫 | 人人爽爽爽 | 中文字幕在线2018 | 久久婷婷六月 | 波多野结衣小视频 | 欧美激情16p | 亚洲黄色中文字幕 | 久久久高清免费视频 | 成人黄色三级视频 | 亚洲天堂久 | 国产野外作爱视频播放 | 狠狠综合久久 | 玩偶姐姐在线观看免费 | 天天摸天天干天天操 | av一区二区免费 | 91看片免费看 | 男人的天堂在线播放 | 国产亚洲视频在线 | avav亚洲| 国产精品久久久久久免费播放 | 亚洲成人av一区二区三区 | 久久久女人 | 得得的爱在线视频 | 四虎最新站名点击进入 | 日本中文字幕久久 | 最近的中文字幕在线看视频 | 一区二区三区欧美在线 | 精品国产91久久久久久 | 国产做受高潮动漫 | 亚洲精品久久久久久久久久久久久 | 亚洲爆乳无码一区二区三区 | 熟女俱乐部一区二区视频在线 | 国产精品成人一区二区三区 | 日本美女高潮 |