原文:CalliCoder — Golang Methods Tutorial with Examples
技術上來說,Go 不是物件導向程式語言。它沒有類別、物件和繼承。
但是,Go 有型別。並且,你可以在型別上定義方法。這讓 Go 有物件導向程式的風格。
讓我們深入了解,看看如何辦到?
Go 方法
方法不過是一種帶有特殊 接收器 參數的函數。
接收器 參數有名稱和型別。它出現在 func
關鍵字和方法名稱之間:
func (receiver Type) MethodName(parameterList) (returnTypes) {
}
接收器可以是 struct 型別,也可以是非 struct 型別。
我們來看一個範例,來了解如何在 type
上定義方法及如何執行這種方法:
package main
import "fmt"
// Struct type - `Point`
type Point struct {
X, Y float64
}
// Method with receiver `Point`
func (p Point) IsAbove(y float64) bool {
return p.Y > y
}
func main() {
p := Point{2.0, 4.0}
fmt.Println("Point : ", p)
fmt.Println("Is Point p located above the line y = 1.0 ? : ", p.IsAbove(1))
}
Point : {2 4}
Is Point p located above the line y = 1.0 ? : true
注意我們在 Point
實體 p
呼叫方法 IsAbove()
的方式。就和你使用物件導向程式語言時呼叫方法的方式一樣。
方法就是函數
因為方法只是帶有接收器參數的函數。我們也可以使用正常的函數來撰寫上述範例:
package main
import "fmt"
// Struct type - `Point`
type Point struct {
X, Y float64
}
func IsAboveFunc(p Point, y float64) bool {
return p.Y > y
}
/*
Compare the above function with the corresponding method -
func (p Point) IsAbove(y float64) bool {
return p.Y > y
}
*/
func main() {
p := Point{2.5, -3.0}
fmt.Println("Point : ", p)
fmt.Println("Is Point p located above the line y = 1.0 ? : ", IsAboveFunc(p, 1))
}
為什麼用方法代替函數?
方法可協助你在 Go 中撰寫物件導向風格的程式碼。方法呼叫比函數呼叫更容易閱讀和理解。
此外,方法可幫助你避免命名衝突。由於方法與特定的接收器綁定,因此你可以在不同接收器型別上使用相同的方法名稱。
我們來看一個範例:
package main
import (
"fmt"
"math"
)
type ArithmeticProgression struct {
A, D float64
}
// Method with receiver `ArithmeticProgression`
func (ap ArithmeticProgression) NthTerm(n int) float64 {
return ap.A + float64(n-1)*ap.D
}
type GeometricProgression struct {
A, R float64
}
// Method with receiver `GeometricProgression`
func (gp GeometricProgression) NthTerm(n int) float64 {
return gp.A * math.Pow(gp.R, float64(n-1))
}
func main() {
ap := ArithmeticProgression{1, 2} // AP: 1 3 5 7 9 ...
gp := GeometricProgression{1, 2} // GP: 1 2 4 8 16 ...
fmt.Println("5th Term of the Arithmetic series = ", ap.NthTerm(5))
fmt.Println("5th Term of the Geometric series = ", gp.NthTerm(5))
}
5th Term of the Arithmetic series = 9
5th Term of the Geometric series = 16
具有指標接收器的方法
我們在上一節中看到的所有範例都有一個值接收器。
對於值接收器,該方法對傳遞給它的值的副本進行操作。因此,呼叫者看不到方法內部對接收器所做的任何修改。
Go 允許你使用指標接收器定義方法
// Method with pointer receiver
func (receiver *Type) MethodName(parameterList) (returnTypes) {
}
帶有指標接收器的方法可以修改接收器指向的值。這種修改對方法的呼叫者也是可見的:
package main
import "fmt"
type Point struct {
X, Y float64
}
/*
Translates the current Point, at location (X,Y), by dx along the x axis and dy along the y axis
so that it now represents the point (X+dx,Y+dy).
*/
func (p *Point) Translate(dx, dy float64) {
p.X = p.X + dx
p.Y = p.Y + dy
}
func main() {
p := Point{3, 4}
fmt.Println("Point p = ", p)
p.Translate(7, 9)
fmt.Println("After Translate, p = ", p)
}
Point p = {3 4}
After Translate, p = {10 13}
將具有指標接收器的方法當作函數
如同具有值接收器的方法,我們也可以將具有指標接收器的方法撰寫為函數。以下範例展示如何使用函數重寫前面的範例:
package main
import "fmt"
type Point struct {
X, Y float64
}
/*
Translates the current Point, at location (X,Y), by dx along the x axis and dy along the y axis
so that it now represents the point (X+dx,Y+dy).
*/
func TranslateFunc(p *Point, dx, dy float64) {
p.X = p.X + dx
p.Y = p.Y + dy
}
func main() {
p := Point{3, 4}
fmt.Println("Point p = ", p)
TranslateFunc(&p, 7, 9)
fmt.Println("After Translate, p = ", p)
}
方法和指標間接取值
1. 具有指標接收器的方法與具有指標參數的函數
具有指標接收器的方法可以接受指標和值作為接收器參數。但是,具有指標參數的函數只能接受指標:
package main
import "fmt"
type Point struct {
X, Y float64
}
// Method with Pointer receiver
func (p *Point) Translate(dx, dy float64) {
p.X = p.X + dx
p.Y = p.Y + dy
}
// Function with Pointer argument
func TranslateFunc(p *Point, dx, dy float64) {
p.X = p.X + dx
p.Y = p.Y + dy
}
func main() {
p := Point{3, 4}
ptr := &p
fmt.Println("Point p = ", p)
// Calling a Method with Pointer receiver
p.Translate(2, 6) // Valid
ptr.Translate(5, 10) // Valid
// Calling a Function with a Pointer argument
TranslateFunc(ptr, 20, 30) // Valid
TranslateFunc(p, 20, 30) // Not Valid
}
注意,
p.Translate()
和ptr.Translate()
呼叫都是有效的。由於 Go 知道Translate()
方法具有指標接收器,因此它將陳述式p.Translate()
翻譯為(&p).Translate()
。它是 Go 為了方便所提供的語法糖。
2. 具有值接收器的方法與具有值參數的函數
具有值接收器的方法可以接受值和指標作為接收器參數。但是,具有值參數的函數只能接受值:
package main
import "fmt"
// Struct type - `Point`
type Point struct {
X, Y float64
}
func (p Point) IsAbove(y float64) bool {
return p.Y > y
}
func IsAboveFunc(p Point, y float64) bool {
return p.Y > y
}
func main() {
p := Point{0, 4}
ptr := &p
fmt.Println("Point p = ", p)
// Calling a Method with Value receiver
p.IsAbove(1) // Valid
ptr.IsAbove(1) // Valid
// Calling a Function with a Value argument
IsAboveFunc(p, 1) // Valid
IsAboveFunc(ptr, 1) // Not Valid
}
在上面的範例中,
p.IsAbove()
和ptr.IsAbove()
陳述式都是有效的。Go 知道IsAbove()
方法有值接收器。因此,為了方便起見,它將陳述式ptr.IsAbove()
翻譯為(*ptr).IsAbove()
。
方法定義限制
請注意,為了能夠在接收器上定義方法,必須在相同 package 中定義接收器型別。
Go 不允許你定義的方法的接收器型別是定義在其他 package 中(這也包括諸如 int
之類的內建型別)。
在前面的所有範例中,struct 和方法都在相同 package main
中定義,因此他們運作良好。但是,如果你嘗試在型別上定義方法,但該型別是在其他 package 中定義的,則會編譯失敗。
我們來看一個範例。細想以下 package 階層:
src/example
main
main.go
model
person.go
以下是 person.go
的內容:
package model
type Person struct {
FirstName string
LastName string
Age int
}
我們來嘗試在 struct Person
上定義一個方法:
package main
import "example/model"
// ERROR: cannot define new methods on non-local types model.Person
func (p model.Person) getFullName() string {
return p.FirstName + " " + p.LastName
}
func main() {
}
在非 struct 型別上定義方法
Go 也允許你在非 struct 型別上定義方法。在以下範例中,我已經在 MyString
型別上定義了一個名為 reverse()
的方法。
package main
import "fmt"
type MyString string
func (myStr MyString) reverse() string {
s := string(myStr)
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
func main() {
myStr := MyString("OLLEH")
fmt.Println(myStr.reverse())
}
# Output
HELLO
結論
希望你喜歡這篇文章。感謝你的閱讀,下篇再見。