本文同時發佈於個人CSDN部落格:https://blog.csdn.net/ggq89/article/details/82084338
"Go語言的物件導向機制與一般語言不同。 它沒有類階層, 甚至可以說沒有類; 僅僅通過組合( 而不是繼承) 簡單的對象來構建複雜的對象。" -- 《Go語言聖經》
1. 結構體嵌入和匿名成員
Go語言提供別樣的 結構體嵌入
機制,讓一個結構體包含另一個結構體類型的 匿名成員
, 這樣就可以通過簡單的點運算子x.f來訪問匿名成員鏈中嵌套的x.d.e.f成員。
Go語言有一個特性讓我們只聲明一個成員對應的資料類型而不指名成員的名字; 這類成員就叫匿名成員。 匿名成員的資料類型必須是命名的(而不是匿名的)類型或指向一個命名的類型的指標。
type Circle struct { Point Radius int} type Wheel struct { Circle Spokes int}
由於有了匿名嵌入的特性, 我們可以直接存取內嵌類型的成員變數而不需要給出完整的路徑:
var w Wheelw.X = 8 // 等價於 w.Circle.Point.X = 8w.Y = 8 // 等價於 w.Circle.Point.Y = 8w.Radius = 5 // 等價於 w.Circle.Radius = 5w.Spokes = 20
同樣的規則,內嵌類型的方法也會提升為外部類型的方法。
2. 匿名組合不是繼承
2.1 方法的接受者沒變
當我們嵌入一個類型,這個類型的方法就變成了外部類型的方法,但是當它被調用時,方法的接受者是內部類型(嵌入類型),而非外部類型。— Effective Go
type Job struct { Command string *log.Logger}func (job *Job)Start() { job.Log("starting now...") ... // 做一些事情 job.Log("started.")}
上面這個Job例子,即使組合後調用的方式變成了job.Log(...),但Log函數的接收者仍然是 log.Logger 指標,因此在Log中也不可能訪問到job的其他成員方法和變數。
2.2 內嵌類型不是基類
如果讀者對基於 類
來實現的物件導向語言比較熟悉的話, 可能會傾向於將 內嵌類型
看作一個基類, 而 外部類型
看作其子類或者繼承類, 或者將 外部類型
看作 "is a"
內嵌類型
。 但這樣理解是錯誤的。
type Point struct{ X, Y float64 }type ColoredPoint struct { Point Color color.RGBA}func (p Point) Distance(q Point) float64 { dX := q.X - p.X dY := q.Y - p.Y return math.Sqrt(dX*dX + dY*dY)}
請注意上面例子中對Distance方法的調用。 Distance有一個參數是Point類型, 但q並不是一個Point類, 所以儘管q有著Point這個內嵌類型, 我們也必須要顯式地選擇它。 嘗試直接傳q的話你會看到錯誤:
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.Distance(q) // compile error: cannot use q (ColoredPoint) as Point
一個ColoredPoint並不是一個Point, 但ColoredPoint "has a"
Point, 並且它有從Point類裡引入的 Distance方法。
實際上,從實現的角度來考慮問題, 內嵌欄位會指導編譯器去產生額外的封裝方法來委託已經聲明好的方法, 和下面的形式是等價的:
func (p ColoredPoint) Distance(q Point) float64 { return p.Point.Distance(q)}
當Point.Distance被以上編譯器產生的封裝方法調用時, 它的接收器值是p.Point, 而不是p。
2.3 匿名衝突(duplicate field) 和隱式名字
匿名成員也有一個隱式的名字,以其類型名稱(去掉包名部分)作為成員變數的名字。 因此不能同一級同時包含兩個類型相同的匿名成員, 這會導致名字衝突。
type Logger struct { Level int}type MyJob struct { *Logger Name string *log.Logger // duplicate field Logger}
以下兩點都間接說明匿名組合不是繼承:
- 匿名成員有隱式的名字
- 匿名可能衝突(duplicate field)
參考文章:
- Effective Go #Embedding: https://golang.org/doc/effective_go.html#embedding
- Golang繼承指標與非指標的一個疑問: https://segmentfault.com/q/1010000002687684/a-1020000002688273
- [golang note] 匿名組合: https://www.cnblogs.com/heartchord/p/5254564.html