Go語言嵌入型別

2020-07-16 10:05:09
Go語言允許使用者擴充套件或者修改已有的型別,這個功能對程式碼複用很重要,在修改已有型別以符合新型別的時候也很重要。這個功能是通過嵌入型別(type embedding)完成的。嵌入型別是將已有的型別直接宣告在新的結構型別裡。被嵌入的型別被稱為新的外部型別的內部型別。

通過嵌入型別,與內部型別相關的識別符號會提升到外部型別上。這些被提升的識別符號就像直接宣告在外部型別裡的識別符號一樣,也是外部型別的一部分。這樣外部型別就組合了內部型別包含的所有屬性,並且可以新增新的欄位和方法。外部型別也可以通過宣告與內部型別識別符號同名的識別符號來覆蓋內部識別符號的欄位或者方法。這就是擴充套件或者修改已有型別的方法。

讓我們通過一個範例程式來演示嵌入型別的基本用法,程式碼如下所示。
// 這個範例程式展示如何將一個型別嵌入另一個型別,以及
// 內部型別和外部型別之間的關係
package main

import (
    "fmt"
)

// user 在程式裡定義一個使用者型別
type user struct {
    name string
    email string
}

// notify 實現了一個可以通過user 型別值的指標
// 呼叫的方法
func (u *user) notify() {
    fmt.Printf("Sending user email to %s<%s>n",
    u.name,
    u.email)
}

// admin 代表一個擁有許可權的管理員使用者
type admin struct {
    user // 嵌入型別
    level string
}

// main 是應用程式的入口
func main() {
    // 建立一個admin 使用者
    ad := admin{
        user: user{
            name: "john smith",
            email: "[email protected]",
        },
        level: "super",
    }

    // 我們可以直接存取內部型別的方法
    ad.user.notify()

    // 內部型別的方法也被提升到外部型別
    ad.notify()
}
在上面程式碼中,我們的程式演示了如何嵌入一個型別,並存取嵌入型別的識別符號。我們從第 10 行和第 24 行中的兩個結構型別的宣告開始。在第 10 行,我們宣告了一個名為 user 的結構型別。在第 24 行,我們宣告了另一個名為 admin 的結構型別。在宣告 admin 型別的第 25 行,我們將 user 型別嵌入 admin 型別裡。

要嵌入一個型別,只需要宣告這個型別的名字就可以了。在第 26 行,我們宣告了一個名為 level 的欄位。注意宣告欄位和嵌入型別在語法上的不同。

一旦我們將 user 型別嵌入 admin,我們就可以說 user 是外部型別 admin 的內部型別。有了內部型別和外部型別這兩個概念,就能更容易地理解這兩種型別之間的關係。

程式碼的 17~21 行,展示了使用 user 型別的指標接收者宣告名為 notify 的方法。這個方法只是顯示一行友好的資訊,表示將郵件發給了特定的使用者以及郵件地址。

現在,讓我們來看一下 main 函數,在 main 函數中展示了嵌入型別背後的機制。在第 32 行,建立了一個 admin 型別的值。內部型別的初始化是用結構字面量完成的。通過內部型別的名字可以存取內部型別。

對外部型別來說,內部型別總是存在的。這就意味著,雖然沒有指定內部型別對應的欄位名,還是可以使用內部型別的型別名,來存取到內部型別的值。

在程式碼的第 41 行,可以看到對 notify 方法的呼叫。這個呼叫是通過直接存取內部型別 user 來完成的。這展示了內部型別是如何存在於外部型別內,並且總是可存取的。不過,借助內部型別提升,notify 方法也可以直接通過 ad 變數來存取。

程式碼中的第 44 行中展示了直接通過外部型別的變數來呼叫 notify 方法。由於內部型別的識別符號提升到了外部型別,我們可以直接通過外部型別的值來存取內部型別的識別符號。讓我們修改一下這個例子,加入一個介面,程式碼如下所示。
// 這個範例程式展示如何將嵌入型別應用於介面
package main

import (
    "fmt"
)

// notifier 是一個定義了
// 通知類行為的介面
type notifier interface {
    notify()
}

// user 在程式裡定義一個使用者型別
type user struct {
    name string
    email string
}

// 通過 user 型別值的指標
// 呼叫的方法
func (u *user) notify() {
    fmt.Printf("Sending user email to %s<%s>n",
    u.name,
    u.email)
}

// admin 代表一個擁有許可權的管理員使用者
type admin struct {
    user
    level string
}

// main 是應用程式的入口
func main() {
    // 建立一個 admin 使用者
    ad := admin{
        user: user{
            name: "john smith",
            email: "[email protected]",
        },
        level: "super",
    }

    // 給 admin 使用者傳送一個通知
    // 用於實現介面的內部型別的方法,被提升到
    // 外部型別
    sendNotification(&ad)
}

// sendNotification 接受一個實現了notifier 介面的值
// 並行送通知
func sendNotification(n notifier) {
    n.notify()
}
上面程式碼所示的範例程式的大部分和之前的程式相同,只有一些小變化,在程式碼的第 8 行,宣告了一個 notifier 介面。之後在第 53 行,有一個 sendNotification 函數,接受 notifier 型別的介面的值。從程式碼可以知道,user 型別之前宣告了名為 notify 的方法,該方法使用指標接收者實現了 notifier 介面。

之後,讓我們看一下 main 函數的改動,這裡才是事情變得有趣的地方。在程式碼的第 37 行,我們建立了一個名為 ad 的變數,其型別是外部型別 admin。這個型別內部嵌入了 user 型別。

之後在第 48 行,我們將這個外部型別變數的地址傳給 sendNotification 函數。編譯器認為這個指標實現了 notifier 介面,並接受了這個值的傳遞。不過如果看一下整個範例程式,就會發現 admin 型別並沒有實現這個介面。

由於內部型別的提升,內部型別實現的介面會自動提升到外部型別。這意味著由於內部型別的實現,外部型別也同樣實現了這個介面。執行這個範例程式,會得到如下所示的輸出。

程式碼清單 5-59 listing56.go 的輸出

Sending user email to john smith<[email protected]>

可以看到程式碼中內部型別的實現被呼叫了。

如果外部型別並不需要使用內部型別的實現,而想使用自己的一套實現,該怎麼辦?讓我們看另一個範例程式是如何解決這個問題的,程式碼如下所示。
// 這個範例程式展示當內部型別和外部型別要
// 實現同一個介面時的做法
package main

import (
    "fmt"
)

// notifier 是一個定義了
// 通知類行為的介面
type notifier interface {
    notify()
}

// user 在程式裡定義一個使用者型別
type user struct {
    name string
    email string
}

// 通過user 型別值的指標
// 呼叫的方法
func (u *user) notify() {
    fmt.Printf("Sending user email to %s<%s>n",
        u.name,
        u.email)
}

// admin 代表一個擁有許可權的管理員使用者
type admin struct {
    user
    level string
}

// 通過 admin 型別值的指標
// 呼叫的方法
func (a *admin) notify() {
    fmt.Printf("Sending admin email to %s<%s>n",
        a.name,
        a.email)
}

// main 是應用程式的入口
func main() {
    // 建立一個 admin 使用者
    ad := admin{
        user: user{
            name: "john smith",
            email: "[email protected]",
        },
        level: "super",
    }

    // 給admin 使用者傳送一個通知
    // 介面的嵌入的內部型別實現並沒有提升到
    // 外部型別
    sendNotification(&ad)

    // 我們可以直接存取內部型別的方法
    ad.user.notify()

    // 內部型別的方法沒有被提升
    ad.notify()
}

// sendNotification 接受一個實現了 notifier 介面的值
// 並行送通知
func sendNotification(n notifier) {
    n.notify()
}
上面程式碼所示的範例程式的大部分和之前的程式相同,只有一些小變化,這個範例程式為 admin 型別增加了 notifier 介面的實現。當 admin 型別的實現被呼叫時,會顯示 "Sending admin email"。作為對比,user 型別的實現被呼叫時,會顯示 "Sending user email"。

main 函數裡也有一些變化,在程式碼的第 46 行,我們再次建立了外部型別的變數 ad。在第 57 行,將 ad 變數的地址傳給 sendNotification 函數,這個指標實現了介面所需要的方法集。

在第 60 行,程式碼直接存取 user 內部型別,並呼叫 notify 方法。最後,在第 63 行,使用外部型別變數 ad 來呼叫 notify 方法。這個範例程式的輸出結果如下所示。

Sending admin email to john smith<[email protected]>
Sending user email to john smith<[email protected]>
Sending admin email to john smith<[email protected]>

這次我們看到了 admin 型別是如何實現 notifier 介面的,以及如何由 sendNotification 函數以及直接使用外部型別的變數 ad 來執行 admin 型別實現的方法。這表明,如果外部型別實現了 notify 方法,內部型別的實現就不會被提升。

不過內部型別的值一直存在,因此還可以通過直接存取內部型別的值,來呼叫沒有被提升的內部型別實現的方法。