Go語言中的代碼重用 - 繼承還是組合?

故事要從我在一個項目中,想要假裝的專業一點而遇到的一個陷阱說起。

代碼重用
在這個項目中,我們已經有了類似如下的代碼:

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語言中是不支持繼承的。

嵌入類型
好吧,只要思想不滑坡,辦法總比困難多。我發現在Go中有一種叫做Embedding的東西。在網上的一些文章中,他們說這就是Go中實現繼承的方式。可是看起來,這更像是Java中的組合,至少語法上像,是不?

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方法用我自己的邏輯實現就好了。這正是我想要的。

繼承?組合?
但是,少俠請留步!我們試一下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
要我說,嵌入類型既不是繼承,也不是組合,只是跟它們都有點像。

多態性
那麼回到我的問題。事實上我一開始就不該嘗試繼承。即使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方法留給多態來實現。

完美。
————————————————
版權聲明:本文爲CSDN博主「力軟快速開發平臺」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_42831704/article/details/90632566

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章