继承是面向对象编程特性之一,一个类可以继承另一个类(单继承)或多个类(多继承),形成了子类与父类的关系。 通过继承子类拥有父类的属性和方法,继承主要的作用是代码重用以及方便扩展
golang的继承实现和其它面向对象编程语言不太一样,它是通过匿名字段实现的,如果在一个结构体中嵌入另一个匿名字段 (结构体),这样就实现了继承,如果是多继承嵌入多个匿名字段 (结构体)
在实际的应用场景中,如果两个或以上的结构体拥有相同类型的字段与方法时,可以把这些相同的字段与方法提取出来放到一个结构体(简单为理解为父类),
然后其它结构体(简单理解为子类)通过匿名方式嵌入到这个结构体中, 子类也可以保留的自己的字段或方法.
注: 其实实现思想是差不多的, 只是实现方式与其它面向对象编程语言不太一样.
一、引出继承
package main
import "fmt"
// Person结构体
type Person struct {
Name string
Age int
sex int
}
// Person结构体方法
func (p *Person) Sayhello() {
fmt.Println("hello: ", p.Name)
}
// Student结构体
type Student struct {
Name string
Age int
sex int
Id int
}
// Student结构体方法
func (t *Student) Sayhello() {
fmt.Println("Hello: ", t.Name)
}
func main() {
// 实例化出p
p := &Person{"BOB", 20, 1}
p.Sayhello()
// 实例化出t
t := &Student{"Smith", 20, 1, 1}
t.Sayhello()
}
package main
import (
"fmt"
)
// Person结构体(父类)
type Person struct {
Name string
Age int
Sex int
}
// Person结构体的Sayhello方法
func (p *Person) Sayhello() {
fmt.Println("hello: ", p.Name)
}
// Student结构体(子类)
type Student struct {
Person // 嵌入匿名字段,Person结构体(也可以是指针结构体*Person)
Id int
}func main() {
// 实例s
s := Student{Person{"BOB", 20, 1}, 1}
// 实例s引用Name、Age、Id字段
fmt.Println("Name字段值: ", s.Person.Name) // Name字段值: BOB
fmt.Println("Name字段值:", s.Name) // Name字段值: BOB
fmt.Println("Age字段值:", s.Age) // Age字段值: 20
fmt.Println("Age字段值:", s.Person.Age) // Age字段值: 20
fmt.Println("Id字段值:", s.Id) // Id字段值: 1
// 实例s引用Sayhello方法
s.Person.Sayhello() // hello: BOB
s.Sayhello() // hello: BOB
// 实例b
var b Student
b.Person.Name = "zhang"
b.Person.Age = 20
b.Name = "lisa"
b.Age = 30
fmt.Println(b.Person.Name, b.Person.Age, b.Name, b.Age) // lisa 30 lisa 30
}
使用细节
实例s引用字段的方式 s.Person.Name、s.Person.Age与s.Name、s.Age是效果是等同的,同样地s.Person.Sayhello()、s.Sayhello()效果也是一样的。因为按照就近原则, 先从当前的结构体始找,然后是嵌入的匿名结构体,如果一定要访问某个匿名结构体需要通过指定实例.匿名结构体名来区分
package main
import (
"fmt"
)
type Boo struct {
Name string
Age int
int // 匿名字段也可以是基本类型
}
func main() {
// a实例
var a Boo
a.Name = "alice"
a.Age = 10
a.int = 1 // 通过名字a.int使用匿名基本类型
// b实例
b := Boo{"bob", 20, 30}
fmt.Println(a) // {alice 10 1}
fmt.Println(b) // {bob 20 30}
}
注:golang中的继承和其它语言实现方式不太一样, 不用太纠结父类与子类之间关系,也不要用其它面向对象语言的去套
二、多继承
多继承的时候就是在一个结构体中嵌入多个匿名结构体
package main
import (
"fmt"
)
// Dad父类
type Dad struct {
Name string //姓名
Age int //年龄
}
// Dad方法
func (d *Dad) dadmethod() {
fmt.Println("dadmethod...")
}
// Mom父类
type Mom struct {
Id int // Id
City string // 城市
}
// Mom方法
func (m *Mom) mommethod() {
fmt.Println("mommehtod...")
}
// Child子类
type Child struct {
Dad
Mom
sex int // 性别
}
// child方法
func (c *Child) childmethod() {
fmt.Println("childmethod....")
}
func main() {
// alice实例
alice := Child{Dad{"alice", 20 }, Mom{10, "beijing"}, 0}
// fmt.Println(alice) //{{alice 20} {10 beijing} 0}
// fmt.Println(alice.Name, alice.Age, alice.Id, alice.City, alice.sex) //alice 20 10 beijing 0
// alice实例调用父类Mom的mommethod方法
alice.mommethod() // mommehtod...
// alice实例调用父类Dad的dadmethod方法
alice.dadmethod() // daammehtod...
// alice实例嗲用自己的childmethod方法
alice.childmethod() // childmethod....
// 实例b
var bob *Child
bob.Name = "bob" // 等同于bob.Dad.Name
bob.Age = 33 // 等同于bob.Dad.Age
bob.Id = 2 // 等同于bob.Mom.Id
bob.City = "Shangahii" // 等同于bob.Mom.City
bob.sex = 1
// fmt.Println(bob) // {{bob 33} {2 Shangahii} 1}
// fmt.Println(bob.Name, bob.Age, bob.Id, bob.City, bob.sex) // {{bob 33} {2 Shangahii} 1}
// bob实例调用父类Mom的mommethod方法
bob.mommethod() // mommethod...
// bob实例调用父类Dad的dadmethod方法
bob.dadmethod() // daammethod...
// bob实例嗲用自己的childmethod方法
bob.childmethod() // childmethod....
}
Child(子类)结构体嵌入Dad、Mom(父类)两个匿名结构体,alice和bob是Child的实例, 它们可以调父类的字段及mommethod、dadmethod方法,这样就实现了多继承
使用细节
如果嵌入的匿名结构体(父类)有相同的字段或方法,当前结构体(子类)的实例在访问时,则需要通过指定匿名结构体类型名来区分,否则报错
package main
import (
"fmt"
)
// Dad父类
type Dad struct {
Name string
Age int
}
// Dad方法
func (d *Dad) method() {
fmt.Println("dadmethod...")
}
// Mom父类
type Mom struct {
Name string
City string
}
// Mom方法
func (m *Mom) method() {
fmt.Println("mommehtod...")
}
// Child子类
type Child struct {
Dad
Mom
}
func main() {
var c Child
// 报错 ambiguous selector c
c.Name = "bob"
c.method()
// 正常
c.Dad.Name = "alice" // 指定Dad类型的Name字段
c.Mom.Name = "hice" // 指定Mom类型的Name字段
c.Dad.method() // 指定Dad类型的method方法
c.Mom.method() // 指定Mom类型的method方法
}
可以看到Dad和Mom结构体有相同的Name字段和method方法, 但 c.Name与c.method这种方式是不行的,因为这样在golang看来不确定是哪个匿名结构体的Name字段与method方法,提示ambiguous selector(模糊的选择),所以必须指定相应的匿名结构体名才能访问,我觉这种方式是比较清晰、合理的。(注: 在python里面多继承按照mro顺序来访问父类的方法,不能指定哪个父类的方法)
无论是单继承还是多次继承主要考虑两点: 就近原则访问、指定匿名结构体名访问
三、方法覆盖
import (
"fmt"
)
// 父类
type Boo struct {
Name string
Age int
}
// sayhello方法
func (b *Boo) sayhello() {
fmt.Println("sayhello:b")
}
// 子类,嵌入匿名结构体Boo
type Foo struct {
Boo
}
// sayhello方法
func (f *Foo) sayhello() {
fmt.Println("sayhello:f")
}
func main() {
var f Foo
// fmt.Println(f)
f.Name = "abc"
f.Age = 1
f.sayhello() // sayhello:f 调用当前结构体(Foo)的sayhello方法
f.Boo.sayhello() // sayhello:b 指定调用匿名结构体(Boo)的sayhello方法,相当于调用父类的方法
}
四、组合
package main
import (
"fmt"
)
// CPu、disk、Mem结构体嵌入到Computer结构体
// cpu结构体
type Cpu struct {
brand string // 品牌
core int // 核心数
ghz float64 // 主频
}
// 磁盘结构体
type Disk struct {
brand string // 品牌
size float64 // 大小
iops float64 // iops
}
// 内存结构体
type Mem struct {
brand string
size float64
}
// 电脑结构体, 嵌入Cpu、Disk、Mem结构体(体现了组合)
type Computer struct {
cpu Cpu
disk Disk
mem Mem
// Computer的Info方法
func (c *Computer) Info() string {
// 可以用其它结构体的字段或方法
return fmt.Sprintf("电脑配置信息如下\ncpu: 品牌 %v 主频 %v 核心数 %v\ndisk: 品牌 %v 大小 %v iops %v\nmem: 品牌 %v 大小 %v\n",
c.cpu.brand, c.cpu.ghz, c.cpu.core, c.disk.brand,
c.disk.size, c.disk.iops,
c.mem.brand, c.mem.size,
)
}
func main() {
// 开始组装电脑,产生Computer结构体指针变量computer
computer := &Computer{
cpu: Cpu{"intel", 8, 2.4},
disk: Disk{"Sandisk", 500.00, 120.21},
mem: Mem{"Sansumg", 8},
}
// 调用info方法
fmt.Println(computer.Info())
}
- 理解就近访问原则,先从当前所属的结构体寻找,然后再是匿名结构体