继承是面向对象编程特性之一,一个类可以继承另一个类(单继承)或多个类(多继承),形成了子类与父类的关系。 通过继承子类拥有父类的属性和方法,继承主要的作用是代码重用以及方便扩展

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()) }
  • 理解就近访问原则,先从当前所属的结构体寻找,然后再是匿名结构体