- Go語言高級編程(第2版)
- 柴樹杉 曹春暉
- 1998字
- 2025-08-07 17:56:13
1.4.2 方法
方法一般是面向對象編程(object-oriented programming,OOP)的一個特性,在C++語言中方法對應一個類對象的成員函數,是關聯到具體對象上的虛表中的。但是Go語言的方法卻是關聯到類型的,這樣可以在編譯階段完成方法的靜態綁定。一個面向對象的程序會用方法來表達其屬性對應的操作,這樣使用這個對象的用戶就不需要直接去操作對象,而是借助方法來做這些事情。面向對象編程進入主流開發領域一般認為是從C++開始的,C++就是在兼容C語言的基礎上支持了類等面向對象的特性。Java編程則號稱是純粹的面向對象語言,因為Java中函數是不能獨立存在的,每個函數都必然是屬于某個類?的。
面向對象編程更多的只是一種思想,很多號稱支持面向對象編程的語言只是將經常用到的特性內置到語言中而已。Go語言的祖先C語言雖然不是一個支持面向對象的語言,但是C語言的標準庫中與文件
相關的函數也用到了面向對象編程的思想。下面我們實現一組C語言風格的與文件
相關的函數:
// 文件對象 type File struct { fd int } // 打開文件 func OpenFile(name string) (f *File, err error) { // ... } // 關閉文件 func CloseFile(f *File) error { // ... } // 讀文件數據 func ReadFile(f *File, offset int64, data []byte) int { // ... }
其中OpenFile()
類似于構造函數,用于打開文件對象,CloseFile
()
類似于析構函數,用于關閉文件對象,ReadFile
()
則類似于普通的成員函數,這3個函數都是普通函數。CloseFile
()
和ReadFile
()
作為普通函數,需要占用包級空間中的名字資源。不過CloseFile
()
和ReadFile
()
函數只是針對File
類型對象的操作,這時候我們更希望這類函數和操作對象的類型緊密綁定在一?起。
Go語言的做法是將函數CloseFile
()
和ReadFile
()
的第一個參數移到函數名的開頭:
// 關閉文件 func (f *File) CloseFile() error { // ... } // 讀文件數據 func (f *File) ReadFile(offset int64, data []byte) int { // ... }
這樣的話,函數CloseFile
()
和ReadFile
()
就成了File
類型獨有的方法了(而不是File
對象方法)。它們也不再占用包級空間中的名字資源,同時File
類型已經明確了它們的操作對象,因此方法名字一般簡化為Close
和Read
:
// 關閉文件 func (f *File) Close() error { // ... } // 讀文件數據 func (f *File) Read(offset int64, data []byte) int { // ... }
將第一個函數參數移到函數前面,從代碼角度看雖然只是一個小的改動,但是從編程哲學角度看,Go語言已經是進入面向對象語言的行列了。我們可以給任何自定義類型添加一個或多個方法。每種類型對應的方法必須和類型的定義在同一個包中,因此是無法給int
這類內置類型添加方法的(因為方法的定義和類型的定義不在一個包中)。對于給定的類型,每個方法的名字必須是唯一的,同時方法和函數一樣也不支持重?載。
方法由函數演變而來,只是將函數的第一個對象參數移到了函數名前面而已。因此,我們依然可以按照原始的過程式思維來使用方法。通過稱為方法表達式的特性可以將方法還原為普通類型的函數:
// 不依賴具體的文件對象 // func CloseFile(f *File) error var CloseFile = (*File).Close // 不依賴具體的文件對象 // func ReadFile(f *File, offset int64, data []byte) int var ReadFile = (*File).Read // 文件處理 f, _ := OpenFile("foo.dat") ReadFile(f, 0, data) CloseFile(f)
有些場景更關心一組相似的操作。例如,Read()
讀取一些數組,然后調用Close()
關閉。在此種場景中,用戶并不關心操作對象的類型,只要能滿足通用的Read()
和Close()
行為就可以了。不過在方法表達式中,因為得到的ReadFile
()
和CloseFile
()
函數參數中含有File
這個特有的類型參數,這使得File
相關的方法無法與其他不是File
類型但是有著相同Read()
和Close()
方法的對象無縫適配。這種小困難難不倒Go語言程序員,我們可以結合閉包特性來消除方法表達式中第一個參數類型的差異:
// 打開文件對象 f, _ := OpenFile("foo.dat") // 綁定到f對象 // func Close() error var Close = func Close() error { return (*File).Close(f) } // 綁定到f對象 // func Read(offset int64, data []byte) int var Read = func(offset int64, data []byte) int { return (*File).Read(f, offset, data) } // 文件處理 Read(0, data) Close()
這剛好是方法值也要解決的問題。我們可以用方法值特性簡化實現:
// 打開文件對象 f, _ := OpenFile("foo.dat") // 方法值:綁定到f對象 // func Close() error var Close = f.Close // 方法值:綁定到f對象 // func Read(offset int64, data []byte) int var Read = f.Read // 文件處理 Read(0, data) Close()
Go語言不支持傳統面向對象中的繼承特性,而是以其特有的組合方式支持了方法的繼承。Go語言中,通過在結構體內置匿名的成員來實現繼承:
import "image/color" type Point struct{ X, Y float64 } type ColoredPoint struct { Point Color color.RGBA }
雖然我們可以將ColoredPoint
定義為一個有3個字段的扁平結構的結構體,但是這里將Point
嵌入ColoredPoint
來提供X
和Y
這兩個字段:
var cp ColoredPoint cp.X = 1 fmt.Println(cp.Point.X) // "1" cp.Point.Y = 2 fmt.Println(cp.Y) // "2"
通過嵌入匿名的成員,不僅可以繼承匿名成員的內部成員,而且可以繼承匿名成員類型所對應的方法。我們一般會將Point
看作基類,把ColoredPoint
看作Point
的繼承類或子類。不過這種方式繼承的方法并不能實現C++中虛函數的多態特性。所有繼承來的方法的接收者參數依然是那個匿名成員本身,而不是當前的變?量。
type Cache struct { m map[string]string sync.Mutex } func (p *Cache) Lookup(key string) string { p.Lock() defer p.Unlock() return p.m[key] }
Cache
結構體類型通過嵌入一個匿名的sync.Mutex
來繼承它的方法Lock()
和Unlock()
。但是在調用p.Lock()
和p.Unlock()
時,p
并不是方法Lock()
和Unlock()
的真正接收者,而是會將它們展開為p.Mutex.Lock()
和p.Mutex.Unlock()
調用。這種展開是編譯時完成的,并沒有運行時代?價。
在傳統的面向對象語言(如C++或Java)的繼承中,子類的方法是在運行時動態綁定到對象的,因此基類實現的某些方法看到的this
可能不是基類類型對應的對象,這個特性會導致基類方法運行的不確定性。而在Go語言通過嵌入匿名的成員來“繼承”的基類方法,this
就是實現該方法的類型的對象,Go語言中方法是編譯時靜態綁定的。如果需要虛函數的多態特性,我們需要借助接口來實?現。
- 多媒體CAI課件設計與制作導論(第二版)
- 動手玩轉Scratch3.0編程:人工智能科創教育指南
- DevOps入門與實踐
- Learning Bayesian Models with R
- Java Web應用開發技術與案例教程(第2版)
- 名師講壇:Spring實戰開發(Redis+SpringDataJPA+SpringMVC+SpringSecurity)
- 數據結構與算法分析(C++語言版)
- PySpark Cookbook
- Python機器學習之金融風險管理
- Kivy Cookbook
- OpenStack Networking Essentials
- App Inventor少兒趣味編程動手做
- OpenCV Android Programming By Example
- Delphi開發典型模塊大全(修訂版)
- Oracle 12c從入門到精通(視頻教學超值版)