Go語言使用空介面實現可以儲存任意值的字典

2020-07-16 10:05:05
空介面可以儲存任何型別這個特性可以方便地用於容器的設計。下面例子使用 map 和 interface{} 實現了一個字典。字典在其他語言中的功能和 map 類似,可以將任意型別的值做成鍵值對儲存,然後進行找回、遍歷操作。詳細實現程式碼如下所示。
package main

import "fmt"

// 字典結構
type Dictionary struct {
    data map[interface{}]interface{} // 鍵值都為interface{}型別
}

// 根據鍵獲取值
func (d *Dictionary) Get(key interface{}) interface{} {
    return d.data[key]
}

// 設定鍵值
func (d *Dictionary) Set(key interface{}, value interface{}) {

    d.data[key] = value
}

// 遍歷所有的鍵值,如果回撥返回值為false,停止遍歷
func (d *Dictionary) Visit(callback func(k, v interface{}) bool) {

    if callback == nil {
        return
    }

    for k, v := range d.data {
        if !callback(k, v) {
            return
        }
    }
}

// 清空所有的資料
func (d *Dictionary) Clear() {
    d.data = make(map[interface{}]interface{})
}

// 建立一個字典
func NewDictionary() *Dictionary {
    d := &Dictionary{}

    // 初始化map
    d.Clear()
    return d
}

func main() {

    // 建立字典範例
    dict := NewDictionary()

    // 新增遊戲資料
    dict.Set("My Factory", 60)
    dict.Set("Terra Craft", 36)
    dict.Set("Don't Hungry", 24)

    // 獲取值及列印值
    favorite := dict.Get("Terra Craft")
    fmt.Println("favorite:", favorite)

    // 遍歷所有的字典元素
    dict.Visit(func(key, value interface{}) bool {

        // 將值轉為int型別,並判斷是否大於40
        if value.(int) > 40 {

            // 輸出很貴
            fmt.Println(key, "is expensive")
            return true
        }

        // 預設都是輸出很便宜
        fmt.Println(key, "is cheap")

        return true
    })
}

值設定和獲取

字典內部擁有一個 data 宇段,其型別為 map。這個 map 的鍵和值都是 interface{} 型別,也就是實現任意型別關聯任意型別。字典的值設定和獲取通過 Set() 和 Get() 兩個方法來完成,引數都是 interface{}。詳細實現程式碼如下所示:
// 字典結構
type Dictionary struct {
    data map[interface{}]interface{} // 鍵值都為interface{}型別
}

// 根據鍵獲取值
func (d *Dictionary) Get(key interface{}) interface{} {
    return d.data[key]
}

// 設定鍵值
func (d *Dictionary) Set(key interface{}, value interface{}) {

    d.data[key] = value
}
程式碼說明如下:
  • 第 3 行,Dictionary 的內部實現是一個鍵值均為 interface{} 型別的 map,map 也具備與 Dictionary 一致的功能。
  • 第 8 行,通過 map 直接獲取值,如果鍵不存在,將返回 nil 。
  • 第 13 行,通過 map 設定鍵值。

遍歷欄位的所有鍵值關聯資料

每個容器都有遍歷操作。遍歷時,需要提供一個回撥返回需要遍歷的資料。為了方便在必要時終止遍歷操作,可以將回撥的返回值設定為 bool 型別,外部邏輯在回撥中不需要遍歷時直接返回 false 即可終止遍歷。

Dictionary 的 Visit() 方法需要傳入回撥函數,回撥函數的型別為 func(k, v interface{}) bool。每次遍歷時獲得的鍵值關聯資料通過回撥函數的 k 和 v 引數返回。Visit 的詳細實現程式碼如下所示:
// 遍歷所有的鍵值,如果回撥返回值為 false,停止遍歷
func (d *Dictionary) Visit(callback func(k, v interface{}) bool) {

    if callback == nil {
        return
    }

    for k, v := range d.data {
        if !callback(k, v) {
            return
        }
    }
}
程式碼說明如下:
  • 第 2 行,定義回撥,型別為 func(k, v interface{}) bool,意思是返回鍵值資料(k、v)。bool 表示遍歷流程控制,返回 true 時繼續遍歷,返回 false 時終止遍歷。
  • 第 4 行,當 callback 為空時,退出遍歷,避免後續程式碼存取空的 callback 而導致的崩潰。
  • 第 8 行,遍歷字典結構的 data 成員,也就是遍歷 map 的所有元素。
  • 第 9 行,根據 callback 的返回值,決定是否繼續遍歷。

初始化和清除

字典結構包含有 map,需要在建立 Dictionary 範例時初始化 map。這個過程通過 Dictionary 的 Clear() 方法完成。在 NewDictionary 中呼叫 Clear() 方法避免了 map 初始化過程的程式碼重複問題。請參考下面的程式碼:
// 清空所有的資料
func (d *Dictionary) Clear() {
    d.data = make(map[interface{}]interface{})
}

// 建立一個字典
func NewDictionary() *Dictionary {
    d := &Dictionary{}

    // 初始化map
    d.Clear()
    return d
}
程式碼說明如下:
  • 第 3 行,map 沒有獨立的復位內部元素的操作,需要復位元素時,使用 make 建立新的範例。Go語言的垃圾回收是並行的,不用擔心 map 清除的效率問題。
  • 第 7 行,範例化一個 Dictionary。
  • 第 11 行,在初始化時呼叫 Clear 進行 map 初始化操作。

使用字典

字典實現完成後,需要經過一個測試過程,檢視這個字典是否存在問題。將一些字串和數值組合放入到字典中,然後再從字典中根據鍵查詢出對應的值,接著再遍歷一個字典中所有的元素。詳細實現程式碼如下所示:
func main() {

    // 建立字典範例
    dict := NewDictionary()

    // 新增遊戲資料
    dict.Set("My Factory", 60)
    dict.Set("Terra Craft", 36)
    dict.Set("Don't Hungry", 24)

    // 獲取值及列印值
    favorite := dict.Get("Terra Craft")
    fmt.Println("favorite:", favorite)

    // 遍歷所有的字典元素
    dict.Visit(func(key, value interface{}) bool {

        // 將值轉為int型別,並判斷是否大於40
        if value.(int) > 40 {

            // 輸出很貴
            fmt.Println(key, "is expensive")
            return true
        }

        // 預設都是輸出很便宜
        fmt.Println(key, "is cheap")

        return true
    })
}
程式碼說明如下:
  • 第 4 行建立字典的範例。
  • 第 7~9 行,將 3 組鍵值對通過字典的 Set() 方法設定到字典中。
  • 第 12 行,根據字串鍵查詢值,將結果儲存在 favorite 中。
  • 第 13 行,列印 favorite 的值。
  • 第 16 行,遍歷字典的所有鍵值對。遍歷的返回資料通過回撥提供,key 是鍵,value 是值。
  • 第 19 行,遍歷返回的 key 和 value 的型別都是 interface{},這裡確認 value 只有 int 型別,所以將 value 轉換為 int 型別判斷是否大於 40。
  • 第 23 和 29 行,繼續遍歷,返回 true
  • 第 23 行,列印鍵。

執行程式碼,輸出結果如下所示:

favorite: 36
My Factory is expensive
Terra Craft is cheap
Don't Hungry is cheap