动态

详情 返回 返回

Golang基礎筆記九之方法與接口 - 动态 详情

本文首發於公眾號: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 头像 manongsir 头像 wnhyang 头像 haoqidedalianmao 头像 immerse 头像 yejianfeixue 头像 kuaidi100api 头像 manshenjiroudehuajuan 头像 wayn111 头像 wanzuqiudeshangba 头像 downtoearth 头像 aizuiyoujie 头像
点赞 15 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.