目录
【类型嵌入】
接口类型嵌入
结构体类型嵌入
【defined 类型与alias类型的方法集合】
defined 类型方法集合
alias 类型方法集合
【如何实现“多态”】
在 Java和PHP中都是使用extends关键字实现类的继承的,那么Go语言中如何实现“继承”呢?其实Go语言是不支持经典面向对象的编程范式与语法元素,说是“继承”,实际上是一种组合的思想,是通过 Go 语言的类型嵌入(Type Embed)来实现的。通过组合定义的新类型与被嵌入的类型之间就没有所谓“父子关系”的概念了。
类型嵌入是在一个类型的定义中嵌入了其他类型,Go 语言支持两种类型嵌入,分别是接口类型嵌入和结构体类型嵌入。接口类型只能嵌入接口类型,而结构体类型可以嵌入任意自定义类型或接口类型。
在一个接口类型定义中嵌入另外一个接口类型的方式,就是接口类型嵌入,如下面代码所示:
package main
type goods interface {
getGoodsList()
getGoodsInfo()
}
type user interface {
getUserList()
getUserInfo()
}
type order interface {
goods
user
getOrderInfo()
}
再比如 比如标准库 io.ReadWriter:
// $GOROOT/src/io/io.go
type ReadWriter interface {
Reader
Writer
}
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
需要注意,在 Go 1.14 版本之前 如果新接口类型嵌入的方法集合的存在交集(也就是重名方法)会报错:
package main
// user 和 userDetail 都包含方法 getUserList()
type user interface {
getUserList()
getUserInfo()
}
type userDetail interface {
getUserList()
}
type orderDetail interface {
user
userDetail // Go 1.14 版本之前会报错: Error: duplicate method getUserList, Go 1.14 版本之后不会报错
}
以某个类型名、类型的指针类型名或接口类型名直接作为结构体字段的方式就是结构体的类型嵌入,这些字段也被叫做嵌入字段。如下面代码所示:
type myInt int
type userSt struct {
name string
age int
}
type userIn interface {
getUserInfo()
}
type result struct {
myInt //自定义类型
userSt //结构体类型
userIn //接口类型
code int
message string
}
func main() {
res := result{
userSt: userSt{
name: "zhangsan",
age: 18,
},
code: 200,
message: "OK",
}
fmt.Println(res) //{0 {zhangsan 18} 200 OK}
fmt.Println(res.userSt.name) //zhangsan
fmt.Println(res.name) //zhangsan
}
上面代码中 res.userSt.name 和 res.name 都能输出对应的值,说明Go语言正是使用组合的思想来实现了“继承”。
但是如果多个嵌入字段的方法集合中存在交集,使用上面的方法获取字段就会报错,如下所示:
type userSt struct {
id string
name string
age int
}
type orderSt struct {
id string
}
type result struct {
userSt //结构体类型
orderSt //结构体类型
}
func main() {
res := result{
userSt: userSt{
id: "1001",
name: "zhangsan",
age: 18,
},
orderSt: orderSt{
id: "123456",
}
}
// fmt.Println(res.id) //报错: ambiguous selector res.id,需要改为下面的方式
fmt.Println(res.userSt.id) //1001
fmt.Println(res.orderSt.id) //123456
}
带有嵌入类型的新类型“继承”了哪些方法?
type T1 struct{}
func (T1) T1M1() { println("T1's M1") }
func (*T1) PT1M2() { println("PT1's M2") }
type T2 struct{}
func (T2) T2M1() { println("T2's M1") }
func (*T2) PT2M2() { println("PT2's M2") }
type T struct {
T1
*T2
}
func main() {
t := T{
T1: T1{},
T2: &T2{},
}
dumpMethodSet(t) //main.T 的方法集合: PT2M2, T1M1, T2M1,
dumpMethodSet(&t) //*main.T 的方法集合: PT1M2, PT2M2, T1M1, T2M1,
}
类型 T 的方法集合 = T1 的方法集合 + *T2 的方法集合;类型 *T 的方法集合 = *T1 的方法集合 + *T2 的方法集合。
只要是通过类型声明语法声明的类型都被称为 defined 类型,新定义的 defined 类型与原 defined 类型是不同的类型。
(1)基于接口类型创建的 defined 类型,它的方法集合 与原接口类型的方法集合 是一致的。
// 基于接口类型创建的 defined 类型
type yourInt int
type yourInterface interface {
M1()
M2()
}
type newInt yourInt // 基于已存在的类型 yourInt 创建新的defined类型 newInt
type newInterface yourInterface // 基于已存在的接口类型 yourInterface 创建新defined接口类型 newInterface
(2)基于结构体类型创建的 defined 类型,它的方法集合 与原接口类型的方法集合 是不同的。
type T1 struct{}
func (T1) T1M1() { println("T1's M1") }
func (*T1) PT1M2() { println("PT1's M2") }
// 基于结构体类型创建的 defined 类型
type myT1 T1
func main() {
var t1 T1
var pt1 *T1
var t2 myT1
var pt2 *myT1
dumpMethodSet(t1) //main.T1 的方法集合: T1M1,
dumpMethodSet(pt1) //*main.T1 的方法集合: PT1M2, T1M1,
dumpMethodSet(t2) //main.myT1 的方法集合为空
dumpMethodSet(pt2) //*main.myT1 的方法集合为空
}
myT1 与 T1 是两个不同的类型,新类型 myT1 并没有“继承” 原 defined 类型 T1 的任何一个方法,新 defined 类型要想实现那些接口,仍然需要重新实现接口的所有方法。
无论原类型是接口类型还是非接口类型,类型别名都与原类型拥有完全相同的方法集合。
type T1 struct{}
func (T1) T1M1() { println("T1's M1") }
func (*T1) PT1M2() { println("PT1's M2") }
// 基于结构体类型创建的 alias 类型
type myT2 = T1
func main() {
var q1 T1
var pq1 *T1
var q2 myT2
var pq2 *myT2
dumpMethodSet(q1) //main.T1 的方法集合: T1M1,
dumpMethodSet(pq1) //*main.T1 的方法集合: PT1M2, T1M1,
dumpMethodSet(q2) //main.T1 的方法集合: T1M1,
dumpMethodSet(pq2) //*main.T1 的方法集合: PT1M2, T1M1,
}
在 PHP和Java的面向对象编程中有个多态,多态就是多种形态,具体点就是当不同的对象去完成某个行为时会产生出不同的状态。比如:都是动物,猫是“喵喵叫”,狗是“汪汪叫”。下面用Go代码模拟实现一下这个场景。
// go中如何实现一个"多态"?
package main
import (
"fmt"
)
type Animal interface {
speak() string
}
type Cat struct{}
func (p *Cat) speak() string {
return "喵喵"
}
type Dog struct{}
type BigDog struct {
Dog
}
func (p *Dog) speak() string {
return "汪汪"
}
func (p BigDog) speak() string {
return "大狗汪汪"
}
func output(p Animal) {
fmt.Printf("%T %v\n", p, p.speak())
}
func main() {
cat := &Cat{} //第一种写法
dog := new(Dog) //第二种写法
bigDog := &BigDog{
Dog{},
}
output(cat) //*main.Cat 喵喵
output(dog) //*main.Dog 汪汪
output(bigDog) //*main.BigDog 大狗汪汪
println()
//指针用 * 表示
var point *Cat = &Cat{}
var struct1 Cat = *point //解引用,得到结构体
fmt.Println(struct1.speak()) //输出:喵喵
var nilCat *Cat // 只是声明了,但是没有使用
fmt.Println(nilCat == nil) //输出:true
println()
}
源代码:https://gitee.com/rxbook/go-demo-2023/tree/master/basic/go02