本文首發於公眾號:Hunter後端
原文鏈接:Golang基礎筆記九之方法與接口
本篇筆記介紹 Golang 裏方法和接口,以下是本篇筆記目錄:
- 方法
- 接口
- 用結構體實現類的功能
1、方法
首先介紹一下方法。
方法是與特定類型關聯的函數,我們在實現一個函數前,綁定一個類型,就實現了這個類型的方法。
比如我們想實現一個結構體的方法,可以如下操作:
type Person struct {
Name string
Age int
}
func (person Person) fmtPersonInfo() {
fmt.Printf("person name is %s, age is %d\n", person.Name, person.Age)
}
在上面的操作中,我們就為 Person 這個結構體綁定了一個方法,而其調用也很簡單,就是實例化一個 Person 結構體後,就可以對其進行調用:
person := Person{Name: "Hunter", Age: 28}
person.fmtPersonInfo()
方法支持的類型
方法支持綁定的類型有結構體、指針類型、接口類型以及自定義類型,但是不支持綁定 Golang 內置的類型包括 int、slice、map 等。
方法綁定到指針類型
前面介紹了方法綁定到結構體上,這裏再介紹一個綁定到指針類型上,還是前面的 Person 結構體,綁定到其指針上,來實現更改 Age 字段的操作:
func (person *Person) ChangeAge(age int) {
person.Age = age
}
person := Person{Name: "Hunter", Age: 28}
person.fmtPersonInfo()
person.ChangeAge(18)
person.fmtPersonInfo()
第二次打印信息就可以看到 person 的 age 已經發生了變化。
這裏需要注意一點,Person 結構體是值類型,如果綁定的是其結構體本身,而非其指針類型,在方法中對其更改後,並不會影響結構體本身,比如下面的操作:
func (person Person) NewChangeAge(age int) {
person.Age = age
}
person := Person{Name: "Hunter", Age: 28}
person.fmtPersonInfo()
person.NewChangeAge(18)
person.fmtPersonInfo()
可以看到,這裏調用 NewChangeAge() 方法後,沒有對 person 這個結構體本身進行更改。
方法綁定到自定義類型
而如果想要綁定 int、slice、map 等內置類型,可以通過方法支持的自定義類型來綁定。
比如我們想要實現使用 slice 綁定一個打印其長度的方法,可以通過自定義類型設置一個別名,通過別名來綁定一個方法:
type MySlce []int
func (mySlice MySlce) printSliceLength() {
fmt.Printf("mySliceLength is %d\n", len(mySlice))
}
slice := MySlce{1, 2, 3}
slice.printSliceLength()
2、接口
1. 接口的定義和實現
接口是一組方法簽名的集合,任何類型只要實現了接口中的所有方法,就被認為實現了該接口。
比如下面我們定義了一個形狀的接口,內部有面積和周長兩個空方法:
type Shape interface {
Area() float64
Perimeter() float64
}
這樣我們就定義了一個接口。
而如果我們要實現這個接口,只需要實現這個接口裏的兩個方法 Area() 和 Perimeter() 就是實現了這個接口,這個過程是隱式的,不需要顯式聲明或者綁定。
接下來我們定義 Rectangle 和 Circle 兩個結構體,並且實現 Area() 和 Perimeter() 兩個方法:
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
type Circle struct {
radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.radius * c.radius
}
func (c Circle) Perimeter() float64 {
return 2 * 3.14 * c.radius
}
我們已經分別用 Rectangle 和 Circle 這兩個結構體實現了 Shape 接口。
那麼這個接口在這裏有什麼作用呢,我們可以實現一個函數,接收接口類型的參數,那麼實現了這個接口的結構體都可以作為傳入:
func PrintShapeInfo(s Shape) {
fmt.Println("Area: ", s.Area())
fmt.Println("Perimeter: ", s.Perimeter())
}
func main() {
r := Rectangle{Width: 2, Height: 3}
PrintShapeInfo(r)
c := Circle{Radius: 5}
PrintShapeInfo(c)
}
2. 類型斷言
類型斷言用於檢查接口值的底層具體類型,並提取該類型的值,其用法示例如下:
value, ok := interfaceValue.(ConcreteType)
- value 是轉換後的具體類型值
- ok 是一個布爾值,表示是否斷言成功
- interfaceValue 是接口類型的變量
-
ConcreteType 是目標具體類型。
比如我們可以修改 PrintShapeInfo 函數,在內部對其進行類型斷言:func PrintShapeInfo(s Shape) { circle, ok := s.(Circle) if ok { fmt.Println("ths shape is circle, the area is: ", circle.Area()) } else { fmt.Println("this shape is not circle") } fmt.Println("Area: ", s.Area()) fmt.Println("Perimeter: ", s.Perimeter()) }
3. 空接口
空接口(interface{}) 可以表示任何類型的值,常用於處理不確定類型的數據。
比如我們想打印一個輸入的變量,但是這個變量的類型不確定,我們可以使用空接口來處理這種情況。
func PrintType(a interface{}) {
switch v := a.(type) {
case int:
fmt.Println("this is int: ", v)
case float64:
fmt.Println("this is float64: ", v)
case string:
fmt.Println("this is string: ", v)
default:
fmt.Println("this is other type: ", v)
}
}
func main() {
PrintType(1)
PrintType(3.4)
PrintType("abc")
}
3、用結構體實現類的功能
在 Golang 裏沒有類的相關定義,但是我們可以使用結構體和方法的組合來實現類的相關特性。
1. 封裝
我們可以通過結構體字段的首字母大小寫控制訪問權限,然後提供公共方法來操作私有字段。
在結構體中,大寫開頭的字段為公開字段,小寫開頭的字段為私有字段。
我們用下面的示例來展示一下用結構體和方法來實現封裝功能。
文件目錄如下:
.
├── main.go
├── service
│ └── person_operation.go
其中 person_operation.go 的內容如下:
package service
type Person struct {
Name string
Age int
gender string
}
func (p *Person) SetGender(gender string) {
p.gender = gender
}
func (p *Person) GetGender() string {
return p.gender
}
其中,Person 這個結構體的 Name 和 Age 字段首字母都為大寫,為公共字段,而 gender 首字母為小寫,在 main.go 裏不能直接引用,所以下面定義了兩個公有接口提供設置和訪問。
以下是 main.go 裏的內容:
package main
import (
"fmt"
"go_proj/service"
)
func main() {
person := service.Person{
Name: "張三",
Age: 18,
// gender: "男", // gender是私有屬性,不能直接訪問
}
// fmt.Println(person.gender) // gender是私有屬性,不能直接訪問
fmt.Println(person.GetGender())
person.SetGender("男")
fmt.Println(person.GetGender())
}
在這裏,gender 字段在 Person 定義和訪問的時候都不能直接操作,需要通過設置的方法來進行定義以及訪問。
2. 繼承
我們可以通過結構體的嵌套來實現繼承,比如下面新建一個 Chinese 結構體:
type Chinese struct {
Person
}
然後我們定義的 Chinese 實例可以調用 Person 結構體的方法:
chinese := service.Chinese{
Person: service.Person{
Name: "張三",
Age: 18,
},
}
chinese.SetGender("男")
fmt.Println(chinese.GetGender())
3. 多態
多態則是同一方法名在不同的類型中有不同的實現,這個操作在前面介紹接口的就已經實現過了,這裏不再做贅述。