這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
在程式設計語言中,方法和函數的概念需要搞清楚。函數指的是一個封裝的代碼塊,我們可以直接調用它,並返回結果。而方法其實也是一種函數,只不過方法需要和某個對象綁定。Golang並沒有類的概念,不過仍然有方法和介面這些概念。
方法
方法接收者
方法接收者是一個特殊參數,給函數指定了這個參數之後,函數就成為方法了。這個特性有點像Kotlin和C#中的擴充方法,定義了帶有接收者的方法之後,接收者這個類型就好像定義了這個方法一樣,我們可以直接在該類型上調用方法。這在功能上,和物件導向的概念是很類似的。
例如下面這樣,定義了一個汽車結構,然後定義了一個接受者方法。然後就可以用物件導向的方式來調用這個方法了。
func Method() { //方法接收者 car := Car{id: 1} car.beep()}type Car struct { id int}func (car Car) beep() { fmt.Printf("Car %v beeps", car.id)}
接收者方法也有一些限制,這也是它和擴充方法之間的區別。接收者方法的接受者類型,必須和接收者方法定義在同一個包中。所以很多非自訂的類型,以及基本類型都不能當做接收者的類型。當然也可以投機取巧,在自己的包中重新為這些類型取個名字即可。
//把基本類型重新定義一下,就可以當做接收者類型了type MyString stringfunc (str MyString) hello() { fmt.Println("hello" + str)}
指標接收者
接收者的類型可以是指標,如果希望在接收者方法中修改接收者的屬性,就需要指標類型了。下面的代碼對Car結構體添加了兩個方法,第一個由於沒有指標類型,所以不會修改原始結構體中的值;而第二個方法會修改汽車的id。
func (car Car) beep() { fmt.Printf("Car %v beeps", car.id)}func (car Car) changeId() { car.id += 1}func (car *Car) changeRealId() { car.id += 1}
介面
聽起來很奇怪,如果Golang沒有類型,為什麼會有介面的概念?讓我們來看看Golang如何解決這些問題。
定義介面
在Golang中,介面就是一組方法簽名的集合。下面就定義了一個介面。
type ICar interface { beep() drive(driver string)}
實現介面
在Golang中,其實並沒有“實現介面”這一說法。在Golang中介面是隱式實現的,也就是說我們不需要implements這些關鍵字。只要一個類型的接收者方法和介面中定義的方法一致,Golang就認為這個類型實現了該介面。下面是一個簡單的例子。
func Interface() { car := MyCar{id: 1} var icar ICar = car icar.beep() icar.drive("yitian")}type ICar interface { beep() drive(driver string)}type MyCar struct { id int}func (car MyCar) beep() { fmt.Printf("car %v beeps\n", car.id)}func (car MyCar) drive(driver string) { fmt.Printf("%v drives car %v\n", driver, car.id)}
空介面
什麼方法都沒定義的介面就是空介面。根據Golang的概念,空介面被任何類型隱式實現,所以空介面可以容納任何類型。
//空介面可以作為任何類型使用type Everything interface {}var e Everything = "123"fmt.Println(e)
類型細化
定義和實現介面是一個類型泛化的過程,在這個過程中,我們抹消掉了類型特有的部分,讓類型公有的部分能夠統一利用。不過有時候需要反過來,將一個介面對象轉換為原始的具體類,讓我們能夠擷取更具體的行為。
現在來看看在Golang中,這件事情應該怎麼做。再次使用上面定義的類型。可以看到和C系語言的括弧強轉方式不同,在Golang中是.(T)類型的文法。
//特化類型myCar := icar.(MyCar)//myCar是MyCar類型變數myCar.beep()
這個文法還有一個攜帶一個成功標誌的版本 t, ok := i.(T)。當成功標誌為真時,表示成功將介面轉換為具體類型,否則表示該介面不是具體類型的執行個體。
如果要進行多次判斷,可以利用switch語句。下面是一個例子。
func testType(i interface{}) { switch i.(type) { case string: fmt.Printf("%v is string\n", i) case int: fmt.Printf("%v is int\n", i) default: fmt.Printf("%v is interface{}\n", i) }}
對這個方法調用多次,可以看到針對不同的類型,方法會返回不同結果。
//類型檢測testType("abc")testType(123)testType(nil)