结构本身用来组织相关数据,可以将处理结构的相关函数定义为方法,类似面向对象程序语言中,使用类定义值域与方法,那么继承呢?Go 语言并非以面向对象为主要典范的语言,没有继承的概念,不过可以使用组合代替继承。
在组合之前
在〈结构与方法〉中使用struct
定义了Account
,如果今天你想定义一个支票帐户,方式之一是…
type CheckingAccount struct {
id string
name string
balance float64
overdraftlimit float64
}
这是个很寻常的作法,也许你想将id
、name
与balance
组织在一起:
package main
import "fmt"
type CheckingAccount struct {
account struct {
id string
name string
balance float64
}
overdraftlimit float64
}
func main() {
checking := CheckingAccount{}
checking.account = struct {
id string
name string
balance float64
}{"1234-5678", "Justin Lin", 1000}
checking.overdraftlimit = 30000
fmt.Println(checking) // {{1234-5678 Justin Lin 1000} 30000}
fmt.Println(checking.account) // {1234-5678 Justin Lin 1000}
fmt.Println(checking.account.name) // Justin Lin
fmt.Println(checking.overdraftlimit) // 30000
}
这是一种方式,不过使用起来麻烦,或许你可以这么做:
package main
import "fmt"
type Account struct {
id string
name string
balance float64
}
type CheckingAccount struct {
account Account
overdraftlimit float64
}
func main() {
checking := CheckingAccount{Account{"1234-5678", "Justin Lin", 1000}, 30000}
fmt.Println(checking) // {{1234-5678 Justin Lin 1000} 30000}
fmt.Println(checking.account) // {1234-5678 Justin Lin 1000}
fmt.Println(checking.account.name) // Justin Lin
fmt.Println(checking.overdraftlimit) // 300000
}
看来还不错,不过,如果想要fmt.Println(checking.name)
就能获取名称的话,这种写法行不通!
结构值域的查找
在定义结构时,可以将另一已定义的结构直接内嵌:
package main
import "fmt"
type Account struct {
id string
name string
balance float64
}
type CheckingAccount struct {
Account
overdraftlimit float64
}
func main() {
account := CheckingAccount{Account{"1234-5678", "Justin Lin", 1000}, 30000}
fmt.Println(account) // {{1234-5678 Justin Lin 1000} 30000}
fmt.Println(account.id) // 1234-5678
fmt.Println(account.name) // Justin
fmt.Println(account.balance) // 1000
fmt.Println(account.overdraftlimit) // 30000
}
这称为类型内嵌(type embedding),Account
被称为CheckingAccount
的内部类型,反之,CheckingAccount
是Account
的外部类型,虽然是透过account.id
、account.name
、account.balance
来访问,不过内部类型提升,令内部类型定义的值域为可见。
那么,如果想要明确地透过Account
的结构来访问呢?也是可以的:
package main
import "fmt"
type Account struct {
id string
name string
balance float64
}
type CheckingAccount struct {
Account
overdraftlimit float64
}
func main() {
account := CheckingAccount{Account{"1234-5678", "Justin Lin", 1000}, 30000}
fmt.Println(account) // {{1234-5678 Justin Lin 1000} 30000}
fmt.Println(account.Account.id) // 1234-5678
fmt.Println(account.Account.name) // Justin
fmt.Println(account.Account.balance) // 1000
fmt.Println(account.overdraftlimit) // 30000
}
虽然内部类型会提升,然而,若外部类型中定义了同名值域,就会直接获取外部类型的值域,因此,如果CheckingAccount
定义了相同的值域balance
,如果透过account.balance
,结果会是找到CheckingAccount
定义的balance
,如果想明确找到Account
的balance
,可以指定Account
作为前置:
package main
import "fmt"
type Account struct {
id string
name string
balance float64
}
type CheckingAccount struct {
Account
balance float64
overdraftlimit float64
}
func main() {
account := CheckingAccount{Account{"1234-5678", "Justin Lin", 1000}, 2000, 30000}
fmt.Println(account.balance) // 2000
fmt.Println(account.Account.balance) // 1000
}
无论是结构值域或是方法,若来自两个结构的值域或方法产生了同名冲突,Go 会有 ambiguous selector 的错误提示,此时你必须明确指定结构名称,指定使用来自哪个结构的值域或方法。
方法的查找
如果内部类型原本定义了方法,这些方法也是查找时的对象:
package main
import (
"errors"
"fmt"
)
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 main() {
account := CheckingAccount{Account{"1234-5678", "Justin Lin", 1000}, 30000}
account.Deposit(2000)
account.Withdraw(500)
fmt.Println(account) // {{1234-5678 Justin Lin 2500} 30000}
}
类似地,若外部类型中定义了同名的方法,那么就会使用该方法,这类似重新定义(Override)的概念:
package main
import (
"errors"
"fmt"
)
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() {
account := CheckingAccount{Account{"1234-5678", "Justin Lin", 1000}, 30000}
account.Deposit(2000)
if err := account.Withdraw(50000); err != nil {
fmt.Println(err)
} else {
fmt.Println(account)
}
}
在上面的范例中,会显示「超出信用额度」的消息,拿掉func (account *CheckingAccount) Withdraw(amount float64)
该函数的定义,则会显示「余额不足」的消息。
如果想指定使用Account
的Withdraw
函数,也还是可以的:
func main() {
account := CheckingAccount{Account{"1234-5678", "Justin Lin", 1000}, 30000}
account.Deposit(2000)
if err := account.Account.Withdraw(50000); err != nil {
fmt.Println(err)
} else {
fmt.Println(account)
}
}
虽然可以实现方法重新定义的概念,不过,单纯只是如上定义的话,并不支持多态的概念,因为一开始这么指定就会出错了:
// cannot use CheckingAccount literal (type CheckingAccount) as type Account in assignment
var account Account = CheckingAccount{Account{"1234-5678", "Justin Lin", 1000}, 30000}
若想实现出多态的概念,必须使用interface
,这在之后的文件会加以说明。