1.6 函式、方法與 receiver
Go 沒有 class,但有函式、struct 與方法。方法只是帶有 receiver 的函式;receiver 讓函式和某個型別形成關聯,進而表達「這個行為屬於這個資料」。
本章目標
學完本章後,你將能夠:
- 區分普通函式與方法
- 理解 receiver 的語法與語義
- 判斷何時使用 pointer receiver
- 用建構函式集中初始化規則
【觀察】普通函式不屬於特定值
普通函式的核心規則是:它不綁定特定型別,只接收參數並回傳結果。以下函式只負責把名稱正規化:
1func NormalizeName(name string) string {
2 return strings.TrimSpace(strings.ToLower(name))
3}呼叫方式:
1name := NormalizeName(" Alice ")這種函式適合描述純粹轉換:輸入什麼,輸出什麼,不需要修改某個物件的內部狀態。
【判讀】方法是帶 receiver 的函式
方法的核心規則是:函式若需要以某個型別作為操作對象,就用 receiver 綁到該型別。行為和某個型別密切相關時,可以寫成方法:
1type Counter struct {
2 value int
3}
4
5func (c *Counter) Inc() {
6 c.value++
7}
8
9func (c Counter) Value() int {
10 return c.value
11}(c *Counter) 和 (c Counter) 就是 receiver。它放在 func 和方法名稱之間,表示這個函式是 Counter 的方法。receiver 只表示「這個函式以這個型別作為操作對象」,不代表繼承關係 — Go 沒有類別繼承,方法只是綁定到型別的函式。
呼叫方式:
1var c Counter
2c.Inc()
3fmt.Println(c.Value())Go 會讓方法呼叫看起來像物件操作,但本質仍然是函式呼叫。
【策略】用是否修改狀態選擇 receiver
receiver 選擇的核心規則是:要修改原值用 pointer receiver,不修改且型別小可以用 value receiver。receiver 有兩種常見形式:
| receiver | 適用情境 |
|---|---|
| value receiver | 不修改原值,型別小,複製成本低 |
| pointer receiver | 需要修改原值,或型別較大,避免複製 |
例如 Value() 不修改 Counter,可以用 value receiver:
1func (c Counter) Value() int {
2 return c.value
3}Inc() 需要修改原本的 counter,所以使用 pointer receiver:
1func (c *Counter) Inc() {
2 c.value++
3}如果你不確定,先問一個問題:這個方法是否要改變 receiver 的狀態?答案是 yes,通常就用 pointer receiver。
【執行】用建構函式集中初始化
建構函式的核心用途是集中初始化規則。Go 沒有 constructor 關鍵字,但慣例會用 NewTypeName 建立需要初始化的型別:
1type Cache struct {
2 items map[string]string
3}
4
5func NewCache() *Cache {
6 return &Cache{
7 items: make(map[string]string),
8 }
9}
10
11func (c *Cache) Set(key, value string) {
12 c.items[key] = value
13}
14
15func (c *Cache) Get(key string) (string, bool) {
16 value, ok := c.items[key]
17 return value, ok
18}使用方式:
1cache := NewCache()
2cache.Set("theme", "dark")
3value, ok := cache.Get("theme")這裡 NewCache() 的價值是保證 items 一定被初始化。呼叫者不需要知道 Cache 內部有 map,也不需要記得手動 make。
函式還是方法?
函式與方法的選擇規則是:純轉換用函式,依賴型別狀態用方法,需要初始化保證用 NewTypeName。可以用這張表判斷:
| 問題 | 選擇 |
|---|---|
| 只是轉換資料? | 普通函式 |
| 行為依賴某個型別的狀態? | 方法 |
| 需要保證初始化規則? | NewTypeName 建構函式 |
| 需要符合 interface? | 方法 |
範例:
1func ParsePort(raw string) (int, error) {
2 // 純資料轉換,適合普通函式
3}
4
5func (s *Server) Start() error {
6 // 啟動 Server,適合方法
7}