golang成員函數設計如下

type T struct {}
func (this T) a() {}
func (this *T) b() {}

我能想到的這個設計的最實用的好處就是

type T2 T
func (this T2) c() {}

不過將函數放在類{}內一樣可以做到這點。我想還有一個可能就是處於美學。

type T struct {
a int
b string

func c() {}
}

也許他們覺得這樣設計並不好看?

golang 並不能跨包為type添加method。例如:

func (this *P.T) a() {}

那可能就是因為美學原因?但也許有些方式看上去會更好看一些?例如。

type T sturct {
a int
b int
} {
c() {}
d() {}
}


我想題主應該已經知道receiver語法糖了,但這並不是method寫在類外的根本原因。

真實的原因之一就是 @大寬寬 老師說的,到底你要attach一個function到*T還是T,這是有區別的,你不能寫在一起。

但這樣好不好呢?這造成了*T和T是兩個method set。

如果你從interface就是contract來想,這就詭異了,Student和它的指針類型是不同的介面類型,需要去服從不同的contract,比如說Student值類型可以玩、喫、睡,但是Student指針類型只能考試。從OO的角度來說,這是反常規的。所以真有人建議,你就乾脆只用指針類型的receiver算了,但這又失去了值類型receiver的容易性。可如果你什麼都用值receiver,這又受到不能修改數據以及過多copy的問題。那這是個糟糕的設計?

其實也不是,因為我們習慣的類型結構是學生繼承人,人繼承動物,於是我們希望人和人的指針的method set是一致的,並一定程度繼承自動物。但go的類型設計偏向於設計成學生是(能喫,能睡,能玩,能考試)四個interface。在我們設計的時候,是增量的,當我需要學生能喫的時候,就加一個eat的method,當需要能玩的時候就加一個play的method。我們每次為學生增加一些功能, 並不影響已有的功能,這也是符合open-close原則的。

那我們再用一個具體的例子,來自於 https://blog.heroku.com/neither-self-nor-this-receivers-in-go

比如說我已經有一個Server類型了,產品經理突然說你這個server要能處理http請求了。你這時不是去給Server找一個父類,而是attach一個新的method。

func (handler *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Render dashboard
}

你可以把這段代碼單獨放在一個文件裏,它是獨立的,它對Server已有的功能最好是透明的,它是自己解釋自己的,也是可以很方便的移到其他類型裏去的。不管你把這段代碼加給誰,誰就是一個http.Handler。這也是分開放另一個好處,我不要this和self,我可以光明正大的給receiver起一個好名字,比如說上面的例子,就叫handler。讓這個method的邏輯更清晰,更好讀。


go本質上反對那種OOP做法的。從go的哲學來講,簡單的東西才能寫正確,好理解和維護。因此在go中並不提供OOP的「類」,而僅僅提供structure和屬於structure的方法。

於是屬於structure的方法可以設定自己到底是希望要(t T)還是(t *T) ,以決定自己到底要不要複製一份數據。

go表達的就是函數就是函數,數據就是數據。與數據綁定的函數提供t.foo()這種寫法。但也僅此而已了。

至於「美」,我沒看出哪種寫法更美的,其實都差不多。只要設計能夠鼓勵更多的正確的,易於維護的代碼,這就是美啊。


這是一種「數據與行為分離」的思想。

struct 只定義數據,不定義行為。所謂方法,其實就是多帶了一個 this 指針的函數,沒有什麼特殊的地方。另一方面,interface 只對行為進行抽象,也不涉及數據。

這樣,數據只與 struct 有關,行為由 interface 進行抽象,由函數實現。兩者互不干涉,十分清晰。


go壓根沒真正的面向對象,只是有點面向對象的感覺,本質是C風格


因為他叫struct不叫類,golang只是提供了面向對象對象的特性,但是並不算是一門嚴格意義上的面向對象編程編程語言。


推薦閱讀:
相關文章