Go語言為任意型別新增方法

2020-07-16 10:05:17
Go語言可以對任何型別新增方法,給一種型別新增方法就像給結構體新增方法一樣,因為結構體也是一種型別。

為基本型別新增方法

在Go語言中,使用 type 關鍵字可以定義出新的自定義型別,之後就可以為自定義型別新增各種方法了。我們習慣於使用程序導向的方式判斷一個值是否為 0,例如:
if  v == 0 {
    // v等於0
}
如果將 v 當做整型物件,那麼判斷 v 值就可以增加一個 IsZero() 方法,通過這個方法就可以判斷 v 值是否為 0,例如:
if  v.IsZero() {
    // v等於0
}
為基本型別新增方法的詳細實現流程如下:
package main

import (
    "fmt"
)

// 將int定義為MyInt型別
type MyInt int

// 為MyInt新增IsZero()方法
func (m MyInt) IsZero() bool {
    return m == 0
}

// 為MyInt新增Add()方法
func (m MyInt) Add(other int) int {
    return other + int(m)
}

func main() {

    var b MyInt

    fmt.Println(b.IsZero())

    b = 1

    fmt.Println(b.Add(2))
}
程式碼輸出如下:

true
3

程式碼說明如下:
  • 第 8 行,使用 type MyInt int 將 int 定義為自定義的 MyInt 型別。
  • 第 11 行,為 MyInt 型別新增 IsZero() 方法,該方法使用了 (m MyInt) 的非指標接收器,數值型別沒有必要使用指標接收器。
  • 第 16 行,為 MyInt 型別新增 Add() 方法。
  • 第 17 行,由於 m 的型別是 MyInt 型別,但其本身是 int 型別,因此可以將 m 從 MyInt 型別轉換為 int 型別再進行計算。
  • 第 24 行,呼叫 b 的 IsZero() 方法,由於使用非指標接收器,b的值會被複製進入 IsZero() 方法進行判斷。
  • 第 28 行,呼叫 b 的 Add() 方法,同樣也是非指標接收器,結果直接通過 Add() 方法返回。

http包中的型別方法

Go語言提供的 http 包裡也大量使用了型別方法,Go語言使用 http 包進行 HTTP 的請求,使用 http 包的 NewRequest() 方法可以建立一個 HTTP 請求,填充請求中的 http 頭(req.Header),再呼叫 http.Client 的 Do 方法,將傳入的 HTTP 請求傳送出去。

下面程式碼演示建立一個 HTTP 請求,並且設定 HTTP 頭。
package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "os"
    "strings"
)

func main() {
    client := &http.Client{}

    // 建立一個http請求
    req, err := http.NewRequest("POST", "http://www.163.com/", strings.NewReader("key=value"))

    // 發現錯誤就列印並退出
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
        return
    }

    // 為檔頭新增資訊
    req.Header.Add("User-Agent", "myClient")

    // 開始請求
    resp, err := client.Do(req)

    // 處理請求的錯誤
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
        return
    }

    data, err := ioutil.ReadAll(resp.Body)
    fmt.Println(string(data))

    defer resp.Body.Close()

}
程式碼執行結果如下:

<html>
<head><title>405 Not Allowed</title></head>
<body bgcolor="white">
<center><h1>405 Not Allowed</h1></center>
<hr><center>nginx</center>
</body>
</html>

程式碼說明如下:
  • 第 11 行,範例化 HTTP 的用戶端,請求需要通過這個用戶端範例傳送。
  • 第 14 行,使用 POST 方式向網易的伺服器建立一個 HTTP 請求,第三個引數為 HTTP 的 Body 部分,Body 部分的內容來自字串,但引數只能接受 io.Reader 型別,因此使用 strings.NewReader() 建立一個字串的讀取器,返回的 io.Reader 介面作為 http 的 Body 部分供 NewRequest() 函數讀取,建立請求只是構造一個請求物件,不會連線網路。
  • 第 24 行,為建立好的 HTTP 請求的頭部新增 User-Agent,作用是表明使用者的代理特性。
  • 第 27 行,使用用戶端處理請求,此時 client 將 HTTP 請求傳送到網易伺服器,伺服器響應請求後,將資訊返回並儲存到 resp 變數中。
  • 第 37 行,讀取響應的 Body 部分並列印。

由於我們構造的請求不是網易伺服器所支援的型別,所以伺服器返回操作不被執行的 405 錯誤。

在本例子第 24 行中使用的 req.Header 的型別為 http.Header,就是典型的自定義型別,並且擁有自己的方法,http.Header 的部分定義如下:
type Header map[string][]string

func (h Header) Add(key, value string) {
    textproto.MIMEHeader(h).Add(key, value)
}

func (h Header) Set(key, value string) {
    textproto.MIMEHeader(h).Set(key, value)
}


func (h Header) Get(key string) string {
    return textproto.MIMEHeader(h).Get(key)
}
程式碼說明如下:
  • 第 1 行,Header 實際是一個以字串為鍵、字串切片為值的對映。
  • 第 3 行,Add() 為 Header 的方法,map 是一個參照型別,因此即便使用 (h Header) 的非指標接收器,也可以修改 map 的值。

為型別新增方法的過程是一個語言層特性,使用型別方法的程式碼經過編譯器編譯後的程式碼執行效率與傳統的程序導向或物件導向的程式碼沒有任何區別,因此,為了程式碼便於理解,可以在編碼時使用Go語言的型別方法特性。

time 包中的型別方法

Go語言提供的 time 包主要用於時間的獲取和計算等,在這個包中,也使用了型別方法,例如:
package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println(time.Second.String())
}
第 9 行的 time.Second 是一個常數,下面程式碼的加粗部分就是 time.Second 的定義:
const (
    Nanosecond  Duration = 1
    Microsecond  = 1000 * Nanosecond
    Millisecond  = 1000 * Microsecond
    Second       = 1000 * Millisecond
    Minute       = 60 * Second
    Hour         = 60 * Minute
)
Second 的型別為 Duration,而 Duration 實際是一個 int64 的型別,定義如下:

type Duration int64

它擁有一個 String 的方法,部分定義如下:
func (d Duration) String() string {

    // 一系列生成buf的程式碼
    …
   
    return string(buf[w:])
}
Duration.String 可以將 Duration 的值轉為字串。