Chinese (Traditional) (中文(繁體)) translation by Qiang Ji (you can also view the original English article)
Go是一個新想法和舊想法的奇特混合。 它有一個非常讓人耳目一新的方法,就是它不怕丟棄已有的概念“如何做事情”。 很多人甚至都不確定Go是否是一個面向對象的語言。 讓我馬上給它做個最終結論吧。 它是的!
在本教程中你將學到在Go語言中所有面向對象設計的複雜性,面向對象編程的柱石像封裝,繼承和多態性在Go語言中是如何被表達的,Go語言是如何跟其它語言比較的。
Go語言設計哲學
Go的根是基於C語言和更廣泛的Algol語言家族。 Ken Thompson半開玩笑地說過,Rob Pike,Robert Granger和他自己聚在一起,決定他們討厭C++語言。 不管這是不是一個笑話,Go語言和C++語言非常的不同。 稍後我們再多聊點這個。 Go語言是關於終極的簡單性。 這在Rob Pike的少即是多中被詳細地解釋過。
Go語言 vs. 其它語言
Go語言沒有類,沒有對象,沒有異常和模板。 它支持垃圾回收和內建的並發。 有關面向對象方面的最顯著的遺漏是Go語言中沒有類型層次結構。 這與大多數面向對象語言如C++,Java,C#,Scala甚至動態語言(如Python和Ruby)形成對比。
Go面向對象語言特性
Go語言沒有類,但它支持類型。 特別是, 它支持structs。 Structs是用戶定義的類型。 Struct類型(含方法)提供類似於其它語言中類的服務。
Structs
一個struct定義一個狀態。 這裡有一個Creature struct。 它有一個Name屬性和一個布爾類型的標誌Real,告訴我們它是一個真實的Creature還是一個虛構的Creature。 Structs只保存狀態,不保存行為。
type Creature struct { Name string Real bool }
方法
方法是對特定類型進行操作的函數。 它們有一個接收器條款,命令它們對什麼樣的類型可進行操作。 這裡是一個Dump()
方法,它可對Creature結構進行操作,並打印出它們的狀態:
func (c Creature) Dump() { fmt.Printf("Name: '%s', Real: %t\n", c.Name, c.Real) }
這是一個不太常見的語法,但是它非常的具體和清晰(不像隱喻的"this"和Python語言中令人困惑的"self")。
嵌入
你可以將匿名的類型嵌入進彼此。 如果你嵌入一個無名的struct那麼被嵌入的struct對接受嵌入的struct直接提供它自己的狀態(和方法)。 比如,FlyingCreature
有一個無名子的被嵌入的Creature
struct,這意味著一個FlyingCreature
就是一個Creature
。
type FlyingCreature struct { Creature WingSpan int }
現在,如果你有一個FlyingCreature的實例,你可以直接訪問它的Name和Real屬性。
dragon := &FlyingCreature{ Creature{"Dragon", false, }, 15, } fmt.Println(dragon.Name) fmt.Println(dragon.Real) fmt.Println(dragon.WingSpan)
接口
接口是Go語言對面向對象支持的標誌。 接口是聲明方法集的類型。 與其它語言中的接口類似,它們不包含方法的實現。
實現所有接口方法的對象自動地實現接口。 它沒有繼承或子類或“implements”關鍵字。 在下面的代碼中,Foo類型實現了Fooer接口(按照慣例,Go接口名稱以“er”結尾)。
type Fooer interface { Foo1() Foo2() Foo3() } type Foo struct { } func (f Foo) Foo1() { fmt.Println("Foo1() here") } func (f Foo) Foo2() { fmt.Println("Foo2() here") } func (f Foo) Foo3() { fmt.Println("Foo3() here") }
面對對象的設計:Go的方式
讓我們看看Go語言如何衡量面向對象編程的支柱:封裝,繼承和多態性。 這些是基於類的編程語言的特徵,它們是最流行的面向對象的編程語言。
在其核心,對像是語言的構造,它們有狀態和行為,這些行為對狀態進行操作並且選擇性地把它們曝露給程序的其它部分。
封裝
Go語言在包的級別進行封裝。 以小寫字母開頭的名稱只在該程序包中可見。 你可以隱藏私有包中的任何內容,只暴露特定的類型,接口和工廠函數。
例如,在這裡要隱藏上面的Foo
類型,只暴露接口,你可以將其重命名為小寫的foo
,並提供一個NewFoo()
函數,返回公共Fooer接口:
type foo struct { } func (f foo) Foo1() { fmt.Println("Foo1() here") } func (f foo) Foo2() { fmt.Println("Foo2() here") } func (f foo) Foo3() { fmt.Println("Foo3() here") } func NewFoo() Fooer { return &Foo{} }
然後來自另一個包的代碼可以使用NewFoo()
並訪問由內部foo
類型實現的Footer
接口:
f := NewFoo() f.Foo1() f.Foo2() f.Foo3()
繼承
繼承或子類化始終是一個有爭議的問題。 實現繼承有許多問題(與接口繼承相反)。 C++和Python以及其它語言實現的多重繼承受著致命的死亡鑽石問題,但即使是單一繼承也會有面對脆弱的基類問題的處境。
現代語言和麵向對象的思維現在傾向於組合而不是繼承。 Go語言對此很嚴肅,它沒有任何類型層次結構。 它允許你通過組合來共享實現的細節。 但Go語言,在一個非常奇怪的轉變中(這可能源於實用的考量),允許嵌入匿名組合。
通過嵌入一個匿名類型的組合等同於實現繼承,這是它所有意圖和目的。 一個嵌入的struct與基類一樣脆弱。 你還可以嵌入一個接口,這相當於在Java或C ++等語言中繼承一個接口。 如果嵌入類型沒有實現所有接口方法,它甚至可能導致產生在編譯時未被發現的運行錯誤。
這裡SuperFoo嵌入Fooer接口,但是SuperFoo沒有實現Foo的方法。 Go編譯器會愉快地讓你創建一個新的SuperFood並調用Fooer的方法,但很顯然這在運行時會失敗。 這會編譯:
type SuperFooer struct { Fooer } func main() { s := SuperFooer{} s.Foo2()
運行此程序會導致一個panic:
panic: runtime error: invalid memory address or nil pointer dereference [signal 0xb code=0x1 addr=0x28 pc=0x2a78] goroutine 1 [running]: panic(0xde180, 0xc82000a0d0) /usr/local/Cellar/go/1.6/libexec/src/runtime/panic.go:464 +0x3e6 main.main() /Users/gigi/Documents/dev/go/src/github.com/oop_test/main.go:104 +0x48 exit status 2 Process finished with exit code 1
多態性
多態性是面向對象編程的本質:只要對象堅持實現同樣的接口,Go語言就能處理不同類型的那些對象。 Go接口以非常直接和直觀的方式提供這種能力。
這裡有一個精心準備的例子,實現Dumper接口的多個creatures(和一個door!)被創建並存儲在一個slice中,然後針對其中每一個調用Dump()
方法。 你會注意到不同實例化對象的風格。
package main import "fmt" type Creature struct { Name string Real bool } func Dump(c*Creature) { fmt.Printf("Name: '%s', Real: %t\n", c.Name, c.Real) } func (c Creature) Dump() { fmt.Printf("Name: '%s', Real: %t\n", c.Name, c.Real) } type FlyingCreature struct { Creature WingSpan int } func (fc FlyingCreature) Dump() { fmt.Printf("Name: '%s', Real: %t, WingSpan: %d\n", fc.Name, fc.Real, fc.WingSpan) } type Unicorn struct { Creature } type Dragon struct { FlyingCreature } type Pterodactyl struct { FlyingCreature } func NewPterodactyl(wingSpan int) *Pterodactyl { pet := &Pterodactyl{ FlyingCreature{ Creature{ "Pterodactyl", true, }, wingSpan, }, } return pet } type Dumper interface { Dump() } type Door struct { Thickness int Color string } func (d Door) Dump() { fmt.Printf("Door => Thickness: %d, Color: %s", d.Thickness, d.Color) } func main() { creature := &Creature{ "some creature", false, } uni := Unicorn{ Creature{ "Unicorn", false, }, } pet1 := &Pterodactyl{ FlyingCreature{ Creature{ "Pterodactyl", true, }, 5, }, } pet2 := NewPterodactyl(8) door := &Door{3, "red"} Dump(creature) creature.Dump() uni.Dump() pet1.Dump() pet2.Dump() creatures := []Creature{ *creature, uni.Creature, pet1.Creature, pet2.Creature} fmt.Println("Dump() through Creature embedded type") for _, creature := range creatures { creature.Dump() } dumpers := []Dumper{creature, uni, pet1, pet2, door} fmt.Println("Dump() through Dumper interface") for _, dumper := range dumpers { dumper.Dump() } }
結論
Go是一個真正的面向對象的編程語言。 它支持基於對象的建模,並鼓勵使用接口而不是具體類型層次結構的最佳實踐。 Go做了一些不尋常的語法選擇,但總體來說,在使用類型,方法和界面方面感覺簡單,輕量和自然。
Go語言的嵌入特性做的不是很純粹,但很明顯這很實用,通過提供嵌入而不是僅僅用名字來實現組合。
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post