在〈結構組合〉的最後討論到了多型,倘若現在需要有個函式,可以接受 Account 與 CheckingAccount 實例,或者是有個陣列或 slice,可以收集 Account 與 CheckingAccount實例,那該怎麼辦呢?
介面定義行為
在 Go 語言中,可以使用 interface 定義行為,舉例來說,若現在想要定義儲蓄的行為,可以如下:
type Savings interface {
    Deposit(amount float64)
    Withdraw(amount float64) error
}
注意,不必使用 func 關鍵字,也不用宣告接受者型態,只需要定義行為的名稱、參數與傳回值。接著該怎麼實現這個介面呢?實際上,就〈結構組合〉,已經實現了這個介面,也就是說,結構上不用任何關鍵字,只要有函式實現這兩個行為就可以了。
因此,現在可以寫個函式,同時接受 Account 與 CheckingAccount 實例,在提款後顯示餘額:
package main
import (
    "errors"
    "fmt"
)
type Savings interface {
    Deposit(amount float64)
    Withdraw(amount float64) error
}
type Account struct {
    id      string
    name    string
    balance float64
}
func (ac *Account) Deposit(amount float64) {
    if amount <= 0 {
        panic("必須存入正數")
    }
    ac.balance += amount
}
func (ac *Account) Withdraw(amount float64) error {
    if amount > ac.balance {
        return errors.New("餘額不足")
    }
    ac.balance -= amount
    return nil
}
type CheckingAccount struct {
    Account
    overdraftlimit float64
}
func (ac *CheckingAccount) Withdraw(amount float64) error {
    if amount > ac.balance+ac.overdraftlimit {
        return errors.New("超出信用額度")
    }
    ac.balance -= amount
    return nil
}
func Withdraw(savings Savings) {
    if err := savings.Withdraw(500); err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(savings)
    }
}
func main() {
    account1 := Account{"1234-5678", "Justin Lin", 1000}
    account2 := CheckingAccount{Account{"1234-5678", "Justin Lin", 1000}, 30000}
    Withdraw(&account1) // 顯示 &{1234-5678 Justin Lin 500}
    Withdraw(&account2) // 顯示 &{{1234-5678 Justin Lin 500} 30000}
}
雖然沒有定義接收者為 *CheckingAccount 的 Deposit 方法,然而,作為內部型態的 Account 有定義 Deposit(並且沒有使用到 CheckingAccount 定義的值域),這個實現被提昇至外部型態,也就滿足了 Savings 要求的行為規範。
注意!由於在實作 Withdraw 與 Deposit 方法時,都是用指標 (ac *Account) 或 (ac *CheckingAccount) 宣告了接受者型態,因此傳遞實例給 func Withdraw(savings Savings) 時,也就必須傳遞指標。
如果在實作Withdraw 與 Deposit 方法時,是使用 (ac Account) 或 (ac CheckingAccount) 宣告了接受者型態,那麼傳遞實例給接受 Savings 的函式時,就可以不用取指標,例如:
package main
import (
    "errors"
    "fmt"
)
type Savings interface {
    Deposit(amount float64)
    Withdraw(amount float64) error
}
type Account struct {
    id      string
    name    string
    balance float64
}
func (ac Account) Deposit(amount float64) {
    if amount <= 0 {
        panic("必須存入正數")
    }
    ac.balance += amount
}
func (ac Account) Withdraw(amount float64) error {
    if amount > ac.balance {
        return errors.New("餘額不足")
    }
    ac.balance -= amount
    return nil
}
type CheckingAccount struct {
    Account
    overdraftlimit float64
}
func (ac CheckingAccount) Withdraw(amount float64) error {
    if amount > ac.balance+ac.overdraftlimit {
        return errors.New("超出信用額度")
    }
    ac.balance -= amount
    return nil
}
func Withdraw(savings Savings) {
    if err := savings.Withdraw(500); err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(savings)
    }
}
func main() {
    account1 := Account{"1234-5678", "Justin Lin", 1000}
    account2 := CheckingAccount{Account{"1234-5678", "Justin Lin", 1000}, 30000}
    Withdraw(account1) // 顯示 {1234-5678 Justin Lin 1000}
    Withdraw(account2) // 顯示 {{1234-5678 Justin Lin 1000} 30000}
}
當然,就這個例子來說,結果並不是正確的,就算改成 Withdraw(&account1) 與 &Withdraw(account2),也不會是正確的結果,因為就 Withdraw 與 Deposit 的接收者來說,會是複製結構的值域,而不是修改原結構實例的值域,這純綷只是示範。
介面實例的型態與值
如果你定義了一個變數:
var savings Savings
那麼 savings 變數儲存了什麼?技術上來說,savings 變數儲存兩個資訊:型態與值。就方才的savings 被指定為 nil 來說,代表著 savings 在底層儲存的型態為 nil,而值沒有指定,這樣的介面實例稱為 nil interface,因為沒有型態資訊,也就不能透過 nil interface 呼叫方法。
如果接收者是定義為 (ac *Account),而且有底下的程式,那麼 savings 底層儲存的型態會 *Account,而值是 Account 結構實例的位址值:
var savings Savings = &Account{"1234-5678", "Justin Lin", 1000}
當接收者是指標時,透過介面比對是否為 nil 時要留意,例如以下會是 true,這是因為 savings 在底層儲存的型態為 nil,而值沒有指定,介面宣告的變數只有在這個情況下,跟 nil 直接相等比較才會是 true:
var savings Savings = nil
fmt.Println(savings == nil)
然而以下會是 false,這是因為 savings 在底層儲存的型態為 *Account,而值是 nil(
這時透過 savings 是可以呼叫方法的,接收者會是 nil,就看你要不要在方法中處理 nil 了):
var acct *Account = nil
var savings Savings = acct
fmt.Println(savings == nil)
這是個 FAQ 了,在〈Why is my nil error value not equal to nil?〉就提到了個例子:
func returnsError() error {
    var p *MyError = nil
    if bad() {
        p = ErrBad
    }
    return p
}
如果對 returnsError 傳回值進行 nil 比較,結果會是 false:
fmt.Println(returnsError() == nil) // false
因此如果傳回型態是個介面,值會是 nil,請記得直接傳 nil:
func returnsError() error {
    if bad() {
        return ErrBad
    }
    return nil // 直接傳 nil
}
如果接收者是定義為 (ac Account),而你有底下的程式:
var savings Savings = Account{"1234-5678", "Justin Lin", 1000}
這時 savings 在底層會儲存型態 Account,而值為結構實例,這時透過 Savings 來進行實例的指定時,底層也會是結構實例的指定,因此會發生複製:
var savings1 Savings = Account{"1234-5678", "Justin Lin", 1000}
var savings2 Savings = savings1
savings2.name = "Monica Huang"
fmt.Println(savings.name) // Justin Lin
異質陣列或 slice
Go 語言會檢查類型的實例,是否實現了介面中規範的行為,若是的話,就可以使用介面型態來接受不同型態實例的指定,因此,若要建立一個異質陣列或 slice,也是可以的:
package main
import (
    "errors"
    "fmt"
)
type Savings interface {
    Deposit(amount float64)
    Withdraw(amount float64) error
}
type Account struct {
    id      string
    name    string
    balance float64
}
func (ac *Account) Deposit(amount float64) {
    if amount <= 0 {
        panic("必須存入正數")
    }
    ac.balance += amount
}
func (ac *Account) Withdraw(amount float64) error {
    if amount > ac.balance {
        return errors.New("餘額不足")
    }
    ac.balance -= amount
    return nil
}
type CheckingAccount struct {
    Account
    overdraftlimit float64
}
func (ac *CheckingAccount) Withdraw(amount float64) error {
    if amount > ac.balance+ac.overdraftlimit {
        return errors.New("超出信用額度")
    }
    ac.balance -= amount
    return nil
}
func main() {
    savingsArray := [...]Savings{
        &Account{"1234-5678", "Justin Lin", 1000},
        &CheckingAccount{Account{"1234-5678", "Justin Lin", 1000}, 30000},
    }
    for _, savings := range savingsArray {
        fmt.Println(savings)
    }
    savingsSlice := []Savings{
        &Account{"1234-5678", "Justin Lin", 1000},
        &CheckingAccount{Account{"1234-5678", "Justin Lin", 1000}, 30000},
    }
    for _, savings := range savingsSlice {
        fmt.Println(savings)
    }
}
在這邊雖然是以 Account 及 CheckingAccount 為例,不過,只要實現了 Savings 的行為,就算是一隻鴨子,也是可以的:
package main
import "fmt"
type Savings interface {
    Deposit(amount float64)
    Withdraw(amount float64) error
}
type Duck struct{}
func (d *Duck) Deposit(amount float64) {
    fmt.Println("我是一隻鴨子,我沒帳戶")
}
func (d *Duck) Withdraw(amount float64) error {
    fmt.Println("我是一隻鴨子,我沒錢")
    return nil
}
func main() {
    duckArray := [...]Savings{
        &Duck{},
        &Duck{},
    }
    for _, duck := range duckArray {
        duck.Deposit(1000)
    }
    duckSlice := []Savings{
        &Duck{},
        &Duck{},
    }
    for _, duck := range duckSlice {
        duck.Withdraw(500)
    }
}
空介面
那麼,如果想要建立一個實例容器,可以收集各種類型的實例,要怎麼做呢?答案就是透過空介面,也就是沒有定義任何行為的 interface {}。
package main
import "fmt"
type Duck struct{}
func main() {
    instances := [](interface{}){
        &Duck{},
        [...]int{1, 2, 3, 4, 5},
        map[string]int{"caterpillar": 123456, "monica": 54321},
    }
    for _, instance := range instances {
        fmt.Println(instance)
    }
}
如果你查看 fmt.Println 的文件說明,可以發現,它的參數類型就是 interface {}:
func Print(a ...interface{}) (n int, err error)
func Printf(format string, a ...interface{}) (n int, err error)
func Println(a ...interface{}) (n int, err error)
順便一提的是,就目前來說,在使用 fmt.Println 顯示結構時,都是使用預設的字串格式,如果想自訂字串格式,必須實現 Stringer 這個介面,這定義在 fmt 的 print.go 之中:
type Stringer interface {
        String() string
}
在需要字串的場合中,會呼叫 String() 方法。例如,若你想要帳號顯示時,可以出現 Account 或 CheckingAccount 字樣的話,可以如下實作:
package main
import "fmt"
type Account struct {
    id      string
    name    string
    balance float64
}
func (ac *Account) String() string {
    return fmt.Sprintf("Account(id = %s, name = %s, balance = %.2f)",
        ac.id, ac.name, ac.balance)
}
type CheckingAccount struct {
    Account
    overdraftlimit float64
}
func (ac *CheckingAccount) String() string {
    return fmt.Sprintf("CheckingAccount(id = %s, name = %s, balance = %.2f, overdraftlimit = %.2f)",
        ac.id, ac.name, ac.balance, ac.overdraftlimit)
}
func main() {
    account1 := Account{"1234-5678", "Justin Lin", 1000}
    account2 := CheckingAccount{Account{"1234-5678", "Justin Lin", 1000}, 30000}
    // 顯示 Account(id = 1234-5678, name = Justin Lin, balance = 1000.00)
    fmt.Println(&account1)
    // 顯示 CheckingAccount(id = 1234-5678, name = Justin Lin, balance = 1000.00, overdraftlimit = 30000.00)
    fmt.Println(&account2)
}
實作某介面的型態有哪些?
來自 Java 之類語言的開發者,在認識 Go 的 interface 後可能會有些疑問,像是「如何知道某個介面的實現型態有哪些?」、「這個型態實現了哪些介面?」…並且會想在文件上尋找這類資訊,因為 Java 的文件中,會記錄某介面的實現類別有哪些。
這是因為 Java 中,介面型態與行為是結合在一起的。
在 Go 中不需要記錄這些,當開發者看到某 API 上定義可以接收某介面型態的值時,應該看看該介面定義了哪些行為,接著看看要傳入的值是否有實作這些行為,這樣就可以了,因為 Go 的介面重點是「行為」,不管 API 上定義的介面型態是什麼,只要行為符合都可以傳入。
也就是說 Go 中,介面型態與行為是分開的,應該重視的只有行為本身,本質上與動態定型語言中只重行為而非型態相同,因此「如何知道某個介面的實現型態有哪些?」、「這個型態實現了哪些介面?」這類問題也就不重要了!

