Go 語(yǔ)言 通過(guò)嵌入結(jié)構(gòu)體來(lái)擴(kuò)展類(lèi)型

2023-03-14 16:54 更新

原文鏈接:https://gopl-zh.github.io/ch6/ch6-03.html


6.3. 通過(guò)嵌入結(jié)構(gòu)體來(lái)擴(kuò)展類(lèi)型

來(lái)看看ColoredPoint這個(gè)類(lèi)型:

gopl.io/ch6/coloredpoint

import "image/color"

type Point struct{ X, Y float64 }

type ColoredPoint struct {
    Point
    Color color.RGBA
}

我們完全可以將ColoredPoint定義為一個(gè)有三個(gè)字段的struct,但是我們卻將Point這個(gè)類(lèi)型嵌入到ColoredPoint來(lái)提供X和Y這兩個(gè)字段。像我們?cè)?.4節(jié)中看到的那樣,內(nèi)嵌可以使我們?cè)诙xColoredPoint時(shí)得到一種句法上的簡(jiǎn)寫(xiě)形式,并使其包含Point類(lèi)型所具有的一切字段,然后再定義一些自己的。如果我們想要的話(huà),我們可以直接認(rèn)為通過(guò)嵌入的字段就是ColoredPoint自身的字段,而完全不需要在調(diào)用時(shí)指出Point,比如下面這樣。

var cp ColoredPoint
cp.X = 1
fmt.Println(cp.Point.X) // "1"
cp.Point.Y = 2
fmt.Println(cp.Y) // "2"

對(duì)于Point中的方法我們也有類(lèi)似的用法,我們可以把ColoredPoint類(lèi)型當(dāng)作接收器來(lái)調(diào)用Point里的方法,即使ColoredPoint里沒(méi)有聲明這些方法:

red := color.RGBA{255, 0, 0, 255}
blue := color.RGBA{0, 0, 255, 255}
var p = ColoredPoint{Point{1, 1}, red}
var q = ColoredPoint{Point{5, 4}, blue}
fmt.Println(p.Distance(q.Point)) // "5"
p.ScaleBy(2)
q.ScaleBy(2)
fmt.Println(p.Distance(q.Point)) // "10"

Point類(lèi)的方法也被引入了ColoredPoint。用這種方式,內(nèi)嵌可以使我們定義字段特別多的復(fù)雜類(lèi)型,我們可以將字段先按小類(lèi)型分組,然后定義小類(lèi)型的方法,之后再把它們組合起來(lái)。

讀者如果對(duì)基于類(lèi)來(lái)實(shí)現(xiàn)面向?qū)ο蟮恼Z(yǔ)言比較熟悉的話(huà),可能會(huì)傾向于將Point看作一個(gè)基類(lèi),而ColoredPoint看作其子類(lèi)或者繼承類(lèi),或者將ColoredPoint看作"is a" Point類(lèi)型。但這是錯(cuò)誤的理解。請(qǐng)注意上面例子中對(duì)Distance方法的調(diào)用。Distance有一個(gè)參數(shù)是Point類(lèi)型,但q并不是一個(gè)Point類(lèi),所以盡管q有著Point這個(gè)內(nèi)嵌類(lèi)型,我們也必須要顯式地選擇它。嘗試直接傳q的話(huà)你會(huì)看到下面這樣的錯(cuò)誤:

p.Distance(q) // compile error: cannot use q (ColoredPoint) as Point

一個(gè)ColoredPoint并不是一個(gè)Point,但他"has a"Point,并且它有從Point類(lèi)里引入的Distance和ScaleBy方法。如果你喜歡從實(shí)現(xiàn)的角度來(lái)考慮問(wèn)題,內(nèi)嵌字段會(huì)指導(dǎo)編譯器去生成額外的包裝方法來(lái)委托已經(jīng)聲明好的方法,和下面的形式是等價(jià)的:

func (p ColoredPoint) Distance(q Point) float64 {
    return p.Point.Distance(q)
}

func (p *ColoredPoint) ScaleBy(factor float64) {
    p.Point.ScaleBy(factor)
}

當(dāng)Point.Distance被第一個(gè)包裝方法調(diào)用時(shí),它的接收器值是p.Point,而不是p,當(dāng)然了,在Point類(lèi)的方法里,你是訪(fǎng)問(wèn)不到ColoredPoint的任何字段的。

在類(lèi)型中內(nèi)嵌的匿名字段也可能是一個(gè)命名類(lèi)型的指針,這種情況下字段和方法會(huì)被間接地引入到當(dāng)前的類(lèi)型中(譯注:訪(fǎng)問(wèn)需要通過(guò)該指針指向的對(duì)象去?。?。添加這一層間接關(guān)系讓我們可以共享通用的結(jié)構(gòu)并動(dòng)態(tài)地改變對(duì)象之間的關(guān)系。下面這個(gè)ColoredPoint的聲明內(nèi)嵌了一個(gè)*Point的指針。

type ColoredPoint struct {
    *Point
    Color color.RGBA
}

p := ColoredPoint{&Point{1, 1}, red}
q := ColoredPoint{&Point{5, 4}, blue}
fmt.Println(p.Distance(*q.Point)) // "5"
q.Point = p.Point                 // p and q now share the same Point
p.ScaleBy(2)
fmt.Println(*p.Point, *q.Point) // "{2 2} {2 2}"

一個(gè)struct類(lèi)型也可能會(huì)有多個(gè)匿名字段。我們將ColoredPoint定義為下面這樣:

type ColoredPoint struct {
    Point
    color.RGBA
}

然后這種類(lèi)型的值便會(huì)擁有Point和RGBA類(lèi)型的所有方法,以及直接定義在ColoredPoint中的方法。當(dāng)編譯器解析一個(gè)選擇器到方法時(shí),比如p.ScaleBy,它會(huì)首先去找直接定義在這個(gè)類(lèi)型里的ScaleBy方法,然后找被ColoredPoint的內(nèi)嵌字段們引入的方法,然后去找Point和RGBA的內(nèi)嵌字段引入的方法,然后一直遞歸向下找。如果選擇器有二義性的話(huà)編譯器會(huì)報(bào)錯(cuò),比如你在同一級(jí)里有兩個(gè)同名的方法。

方法只能在命名類(lèi)型(像Point)或者指向類(lèi)型的指針上定義,但是多虧了內(nèi)嵌,有些時(shí)候我們給匿名struct類(lèi)型來(lái)定義方法也有了手段。

下面是一個(gè)小trick。這個(gè)例子展示了簡(jiǎn)單的cache,其使用兩個(gè)包級(jí)別的變量來(lái)實(shí)現(xiàn),一個(gè)mutex互斥量(§9.2)和它所操作的cache:

var (
    mu sync.Mutex // guards mapping
    mapping = make(map[string]string)
)

func Lookup(key string) string {
    mu.Lock()
    v := mapping[key]
    mu.Unlock()
    return v
}

下面這個(gè)版本在功能上是一致的,但將兩個(gè)包級(jí)別的變量放在了cache這個(gè)struct一組內(nèi):

var cache = struct {
    sync.Mutex
    mapping map[string]string
}{
    mapping: make(map[string]string),
}


func Lookup(key string) string {
    cache.Lock()
    v := cache.mapping[key]
    cache.Unlock()
    return v
}

我們給新的變量起了一個(gè)更具表達(dá)性的名字:cache。因?yàn)閟ync.Mutex字段也被嵌入到了這個(gè)struct里,其Lock和Unlock方法也就都被引入到了這個(gè)匿名結(jié)構(gòu)中了,這讓我們能夠以一個(gè)簡(jiǎn)單明了的語(yǔ)法來(lái)對(duì)其進(jìn)行加鎖解鎖操作。



以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)