Go起手式之三


Posted by Nacho on 2020-04-08

對於Go是不是物件導向的程式語言,我在剛開始學習的時候也抱著這個疑惑。
它有C語言的影子但有同時有不確定它與物件導向之間的關係,所以Go到底是不是物件導向呢?來查一下,官方回答會得到「Yes and no.」,換句話說,想當它是也可以不是也行...沒有那麼重要...或許

目標

理解Go世界中對屬性(attribute)和方法(method)的活用;pass by value和pass by reference的差異與做法,並要知道如何一些編譯錯誤的原因。

Method

物件導向程式有類別(class)與物件(object)的觀念

  • 類別和物件就如同餅乾模子和餅乾的關係,用一個模子可以產生很多餅乾
  • 在類別中會定義屬性(attribute)和方法(method),例如餅乾的price是屬性,getPrice()是方法
  • 變成物件後,屬性變成物件的狀態(state),方法變成物件的行為(behaviour)

然而,在Go的世界裡,沒有完全相似類別(class)的概念! 屬性(attribute)由struct取代;方法(method)是透過func加上自定義的struct;變數透過宣告實例化,如同物件獲得了狀態並透過不同的方法產生如物件的行為。

package main

import (
    "fmt"
    "math"
)
// 透過Vertex結構產生節點物件
type Vertex struct {
    X, Y float64
}
// 傳入v的值,這個值是一個struct的型態
func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := Vertex{3, 4} 
    fmt.Println(v.Abs())  // 結果 5
}

Pointer Receiver/ Value Receiver

延續前一個例子,這裡我加入Pointer Receiver的method叫Scale。Scale()採用Pointer Reciever,它直接指向v的位置且能夠對裏頭的X和Y的值進行修改(pass by reference),相較於,Abs()採用的是Value Recieiver,它得到的是v的備份內容,並在這備份上修改(pass by value),不會影響原來的v。實務上,Pointer Receiver會比Value Receiver更常用到。

...
// pass by value/ Value receiver
func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// pass by reference/ Pointer receiver
func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func main() {
    v := Vertex{3, 4}
    v.Scale(10)
    fmt.Println(v.Abs()) // 50
    fmt.Println(v) // {30 40}
}

Method 本身也是func

這裡將method的寫法變化一下,由於method本身就是函式,因此,除了把Receiver置於方法名稱前,也可以置於名稱後的括弧中。

...
// 將Value Receiver放在Abs()中
func Abs(v Vertex) float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// 將Pointer Receiver放在Scale()中
func Scale(v *Vertex, f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func main() {
    v := Vertex{3, 4}
    // 注意其使用方式也不一樣!!
    Scale(&v, 10)
    fmt.Println(Abs(v)) // 50
    fmt.Println(v) // {30 40}
}

活用Method

Pointer Receiver Method

對於使用Pointer Receiver的方法而言,即使v是值(value),Scale()也會在執行編譯時將v.Sacle(5)自動轉換成(&v).Scale(5)。

var v Vertex
v.Scale(5)  // OK
p := &v
p.Scale(10) // OK

以下是使用Pointer Receiver Method的例子,特別注意到v是value和p是指標,兩者都可以使用Scale(),而不會產生編譯問題。但如果是採用ScaleFunc()就要特別注意了!

func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func ScaleFunc(v *Vertex, f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func main() {
    v := Vertex{3, 4}
    v.Scale(2) // v是value,將v.Scale(2)自動轉換成(&v).Scale(2)
    ScaleFunc(&v, 10) // 注意,&v

    p := &Vertex{4, 3}
    p.Scale(3) // p是pointer
    ScaleFunc(p, 8) // 注意,p

    fmt.Println(v, p) // {60 80} &{96 72}
    fmt.Println(v, *p) // {60 80} {96 72}
}

Value Receiver Method

對於使用Value Receiver的方法而言,v是值(value),Abs()可以直接使用,p是指標(pointer)的話,p.Abs()在執行編譯時會自動轉換成(*p).Abs()。

var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK

以下是使用Value Receiver Method的例子,特別注意到v是value和p是指標,Abs(),而不會產生編譯問題。如果是採用AbsFunc()就要特別注意了!

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func AbsFunc(v Vertex) float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(v.Abs()) // 5
    fmt.Println(AbsFunc(v)) // 5

    p := &Vertex{4, 3}
    fmt.Println(p.Abs()) // 5
    fmt.Println(AbsFunc(*p)) // 5
}

參考

Go的官方文件


#Go Method #Pointer Receiver #Value Receiver







Related Posts

網路溝通、HTTP 協定

網路溝通、HTTP 協定

學 JavaScript 的那些筆記

學 JavaScript 的那些筆記

政大傳院院導師活動講座心得:余佩真的多重宇宙 影視歌三棲的創作人生

政大傳院院導師活動講座心得:余佩真的多重宇宙 影視歌三棲的創作人生


Comments