Stories

Detail Return Return

Golang基礎筆記九之方法與接口 - Stories Detail

本文首發於公眾號:Hunter後端

原文鏈接:Golang基礎筆記九之方法與接口

本篇筆記介紹 Golang 裏方法和接口,以下是本篇筆記目錄:

  1. 方法
  2. 接口
  3. 用結構體實現類的功能

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)
  1. value 是轉換後的具體類型值
  2. ok 是一個布爾值,表示是否斷言成功
  3. interfaceValue 是接口類型的變量
  4. 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. 多態

多態則是同一方法名在不同的類型中有不同的實現,這個操作在前面介紹接口的就已經實現過了,這裏不再做贅述。

user avatar u_17494575 Avatar manongsir Avatar wnhyang Avatar haoqidedalianmao Avatar immerse Avatar yejianfeixue Avatar kuaidi100api Avatar manshenjiroudehuajuan Avatar wayn111 Avatar wanzuqiudeshangba Avatar downtoearth Avatar aizuiyoujie Avatar
Favorites 15 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.