故事要從我在一個項目中,想要僞裝的專業一點而遇到的一個陷阱提及。編程
在這個項目中,咱們已經有了相似以下的代碼:ide
package main import ( "fmt" ) func main() { user := &User{name: "Chris"} user.sayHi() } type User struct { name string } func (u *User) sayHi() { u.sayName() u.sayType() } func (u *User) sayName() { fmt.Printf("I am %s.", u.name) } func (u *User) sayType() { fmt.Println("I am a user.") }
I am Chris.I am a user.
而後我接到的新需求是這樣的,我須要開發一種新的用戶,它和當前這種用戶有一些相同的行爲。固然,最主要的是也有不少不一樣的行爲。做爲一名老司機,我固然知道,這些不一樣的地方纔是我須要重點關注而且實現的。
爲了區分這兩種用戶,咱們就叫他們普通用戶和文藝用戶吧。
由於咱們已經有了普通用戶的實現代碼了,做爲一個資深(誤)Java工程師,我想經過繼承這個普通用戶來實現代碼的複用。然而悲傷辣麼大,我發如今Go語言中是不支持繼承的。ui
好吧,只要思想不滑坡,辦法總比困難多。我發如今Go中有一種叫作Embedding的東西。在網上的一些文章中,他們說這就是Go中實現繼承的方式。但是看起來,這更像是Java中的組合,至少語法上像,是不?code
package main import ( "fmt" ) func main() { artisticUser := &ArtisticUser{User: &User{name: "Chris"}} artisticUser.sayName() artisticUser.sayType() } type User struct { name string } func (u *User) sayHi() { u.sayName() u.sayType() } func (u *User) sayName() { fmt.Printf("I am %s.", u.name) } func (u *User) sayType() { fmt.Println("I am a user.") } type ArtisticUser struct { *User } func (u *ArtisticUser) sayType() { fmt.Println("I am an artistic user.") }
I am Chris.I am an artistic user.
幹得漂亮!這樣我就能夠複用User的sayName方法,只要把sayType方法用我本身的邏輯實現就行了。這正是我想要的。orm
可是,少俠請留步!咱們試一下sayHi方法看看會發生什麼?對象
package main import ( "fmt" ) func main() { artisticUser := &ArtisticUser{User: &User{name: "Chris"}} artisticUser.sayHi() } type User struct { name string } func (u *User) sayHi() { u.sayName() u.sayType() } func (u *User) sayName() { fmt.Printf("I am %s.", u.name) } func (u *User) sayType() { fmt.Println("I am a user.") } type ArtisticUser struct { *User } func (a *ArtisticUser) sayType() { fmt.Println("I am an artistic user.") }
I am Chris.I am a user.
這不科學!在Java裏,子類老是會調用本身的方法的(已經override了父類的方法)。除非子類沒有覆蓋父類的方法,纔會使用從父類繼承來的方法。
在這個例子中,我override了(其實Go中沒有這個概念)sayType方法,可是當咱們在sayHi中調用它時,卻沒有調用這個override方法,而是用了父類的原始方法。繼承
實際上,類型嵌入不是繼承。它只是某種形式上的語法糖而已。在面向對象編程中,子類應該是能夠被當作父類來使用的。在里氏替換原則中,子類應該能在任何須要的地方替換掉父類。(注意一點,咱們這裏一開始嘗試覆蓋父類的非抽象方法已經違背了里氏替換原則)。
可是在上邊的例子中,ArtisticUser和User是兩種不一樣的類型。且不能替換使用。接口
package main import ( "fmt" ) func main() { user := &User{name: "Chris"} artisticUser := &ArtisticUser{User: user} fmt.Printf("user's type is: %T\n", user) fmt.Printf("artisticUser's type is: %T\n", artisticUser) acceptUser(user) //acceptUser(artisticUser) } type User struct { name string } func (u *User) sayHi() { u.sayName() u.sayType() } func (u *User) sayName() { fmt.Printf("I am %s.", u.name) } func (u *User) sayType() { fmt.Println("I am a user.") } type ArtisticUser struct { *User } func (a *ArtisticUser) sayType() { fmt.Println("I am an artistic user.") } func acceptUser(u *User) { }
user's type is: *main.User artisticUser's type is: *main.ArtisticUser
若是你嘗試去掉註釋掉的那一行,你會獲得一個build錯誤:開發
cannot use artisticUser (type *ArtisticUser) as type *User in argument to acceptUser
要我說,嵌入類型既不是繼承,也不是組合,只是跟它們都有點像。string
那麼回到個人問題。事實上我一開始就不應嘗試繼承。即便Go提供了繼承機制,覆蓋一個父類的非抽象方法也將破壞里氏替換原則。我一開始想要試試繼承實際上是一種偷懶的行爲,由於我並不想重構已有的那麼一大坨代碼。
可是咱們不該該懼怕重構。你看,就算我想試着逃避重構,仍是掉進別的溝裏了。
若是能重來,我要選李白。。。呸,若是能讓我重構已有代碼的話,也許我能夠試試接口。在Go語言中,接口很是靈活,是實現多態的手段。
package main import ( "fmt" ) func main() { user := &User{name: "Chris"} user.ISubUser = &NormalUser{} user.sayHi() user.ISubUser = &ArtisticUser{} user.sayHi() } type ISubUser interface { sayType() } type User struct { name string ISubUser } func (u *User) sayHi() { u.sayName() u.sayType() } func (u *User) sayName() { fmt.Printf("I am %s.", u.name) } type NormalUser struct { } func (n *NormalUser) sayType() { fmt.Println("I am a normal user.") } type ArtisticUser struct { } func (a *ArtisticUser) sayType() { fmt.Println("I am an artistic user.") }
I am Chris.I am a normal user. I am Chris.I am a artistic user.
這樣我就重用了sayName和sayHi方法,而且把sayType方法留給多態來實現。
完美。