继承是面向对象编程特性之一,一个类可以继承另一个类(单继承)或多个类(多继承),形成了子类与父类的关系。 通过继承子类拥有父类的属性和方法,继承主要的作用是代码重用以及方便扩展
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()) }
- 理解就近访问原则,先从当前所属的结构体寻找,然后再是匿名结构体