1. 封装

封装(encapsulation)是将字段和对字段的操作封装到一个特定代码块中,数据被保护在内部,这样其它程序想要访问或操作这些数据必须通道特定的、被授权的方法!封装有利于隐藏实例的细节,且可以对数据进行校验,保证数据的安全性。
在Go的开发中,并不是特别强调封装,这点与其它的OOP语言有所区别。Go中的实现方式如下:

  • 将结构体、字段的首字母小写,这样改结构体或者字段就不能导出,变成了包中的私有的
  • 结构体小写,并提供一个首字母大写的构造函数(工厂函数),这样就能通过构造函数去实例化
  • 提供 Set()Get() 方法,实现对数据操作的封装和校验 ```go package person

import ( “fmt” )

type person struct { Name string age int }

func NewPerson(name string) *person { // 以下两种写法都是可以的,部分编辑器可能会提示有异常 //p := new(person) //p.Name = name //return p return &person{ Name: name, } }

// 对数据进行校验 func (p *person) SetAge(age int) { if age > 0 && age < 200 { p.age = age return } fmt.Println(“输入的年龄不合法”) }

func (p *person)GetAge() int { return p.age }

  1. ```go
  2. package main
  3. import (
  4. "fmt"
  5. "studygo/day06/01-enc01/person"
  6. )
  7. func main() {
  8. tom := person.NewPerson("Tom")
  9. fmt.Printf("%T %v\n", tom, tom)
  10. tom.SetAge(100)
  11. fmt.Printf("%T %v\n", tom, tom.GetAge())
  12. }
  1. [root@heyingsheng studygo]# go run day06/01-enc01/main/main.go
  2. *person.person &{Tom 0}
  3. *person.person 100

2. 继承

继承的目的是实现代码的复用,继承可以实现字段的继承和方法的继承,Go的继承是通过结构体嵌套实现的!Go中继承的实现逻辑如下:

  • 将不同结构体中相同的属性和方法抽象出来,组成一个新的结构体和结构体方法
  • 其它结构体在定义时,将该结构体作为一个字段,这样实现继承该抽象出来的结构体和方法

    2.1. 继承的简单案例

    在下面的案例中,将玩家、怪物和其它生物的共有数据提取出来,生成一个Animal的结构体,其它的所有怪物和玩家都继承Animal的方法和属性,这样就将相同的代码合并和简化! ```go package main

import “fmt”

// 生物信息 type Animal struct { Name string Blood int } // 玩家信息 type Player struct { Animal // 匿名结构体 San int Hunger int }

func (a *Animal)Sleep() { fmt.Printf(“%s在睡觉!\n”, a.Name) }

func (p *Player)Speak() { fmt.Printf(“%s在说话!\n”, p.Name) }

func (p *Player)Info() { fmt.Printf(“玩家%s的血量:%v, San: %v,饱食度:%v\n”, p.Name, p.Blood, p.San, p.Hunger) }

func main() { wendy := Player{ Animal: Animal{Name:”wendy”, Blood:150}, San: 200, Hunger: 150, } wendy.Sleep() wendy.Speak() wendy.Info() }

  1. ```
  2. [root@heyingsheng studygo]# go run day06/02-inherit/mian.go
  3. wendy在睡觉!
  4. wendy在说话!
  5. 玩家wendy的血量:150, San: 200,饱食度:150

2.2. 继承的注意事项

2.2.1. 匿名结构体中字段和属性访问方式

在下面的案例中,Student继承了Person结构体,且Person和Student中有同名的字段Name,此时s.Name会指向Student中的Name,而Student中没有Age,s.Age会指向Person中的Age,这是继承中的就近原则!在方法中也是如此!
或者说,原本就是 s.Person.Age 和 s.Person.Name 才能访问到Person中的属性,Go为此做了简化,如果Student中没有的字段或方法,解释器会继续向父类寻找,直到顶层还没有就报错!

  1. package main
  2. import "fmt"
  3. type Person struct {
  4. Name string
  5. Age int
  6. }
  7. type Student struct {
  8. Person
  9. Name string
  10. Class string
  11. }
  12. func (s *Student)String() string {
  13. return fmt.Sprintf("[s.name=%v; s.Class=%v: s.Age=%v, s.Person.Name=%v, s.Person.Age=%v]",
  14. s.Name, s.Class, s.Age, s.Person.Name, s.Person.Age)
  15. }
  16. func main() {
  17. s0 := Student{
  18. Person: Person{Name:"P-zhangsan", Age:18},
  19. Name: "S-张三",
  20. Class: "大一",
  21. }
  22. fmt.Println(&s0)
  23. }
  1. [root@heyingsheng studygo]# go run day06/02-inherit/no1.go
  2. [s.name=S-张三; s.Class=大一: s.Age=18, s.Person.Name=P-zhangsan, s.Person.Age=18]

2.2.2. 多重继承中属性和方法的访问

在下面案例中,可以看到,在多重继承中,如果两个父类的属性有重名(Name, Age),且子类没有这个属性(Age),那么必须要明确指定是哪个父类的属性(Age): line26, line27 。方法也是如此!

  1. package main
  2. import "fmt"
  3. type A struct {
  4. Name string
  5. Age int
  6. }
  7. type B struct {
  8. Name string
  9. Age int
  10. Sal float64
  11. }
  12. type C struct {
  13. A
  14. B
  15. Name string
  16. }
  17. func main() {
  18. var c C
  19. c.Name = "张三" // c.Name
  20. c.Sal = 10000 // c.B.Sal
  21. //c.Age = 18 // 报错 ambiguous selector c.Age 模糊的选择器 c.Age
  22. c.A.Age = 18
  23. fmt.Printf("%v\n", c)
  24. }

2.2.3. 有名结构体(组合)

上面提到的继承都是继承自匿名结构体,如果是有名结构体,那么称为组合,其实是一个特殊的继承方式。这种继承方式下,想要访问父类的方法和属性,必须要明确指定哪个父类,不可以简写!如下案例对别上面的案例分析。

  1. package main
  2. import "fmt"
  3. // 生物信息
  4. type Animal struct {
  5. Name string
  6. Blood int
  7. }
  8. // 玩家信息
  9. type Player struct {
  10. A Animal // 有名结构体
  11. San int
  12. Hunger int
  13. }
  14. func (a *Animal) Sleep() {
  15. fmt.Printf("%s在睡觉!\n", a.Name)
  16. }
  17. func (p *Player) Speak() {
  18. //fmt.Printf("%s在说话!\n", p.Name) //p.Name undefined (type *Player has no field or method Name)
  19. fmt.Printf("%s在说话!\n", p.A.Name)
  20. }
  21. func (p *Player) Info() {
  22. fmt.Printf("玩家%s的血量:%v, San: %v,饱食度:%v\n", p.A.Name, p.A.Blood, p.San, p.Hunger)
  23. }
  24. func main() {
  25. wendy := Player{
  26. A: Animal{Name: "wendy", Blood: 150},
  27. San: 200,
  28. Hunger: 150,
  29. }
  30. wendy.A.Sleep()
  31. wendy.Speak()
  32. wendy.Info()
  33. }

2.3. 方法继承和重写实现思路

2.3.1. 方法继承

  1. package main
  2. import "fmt"
  3. type service struct {
  4. name string
  5. port uint16
  6. }
  7. type database struct {
  8. service
  9. dbType string
  10. dataDir []string
  11. logDir string
  12. }
  13. func (s service)manager(operate string) {
  14. switch operate {
  15. case "start":
  16. fmt.Printf("%s start!\n", s.name)
  17. case "stop":
  18. fmt.Printf("%s stop!\n", s.name)
  19. }
  20. }
  21. func (d database)backup() {
  22. fmt.Printf("%s backup success!\n", d.name)
  23. }
  24. func main() {
  25. mysql := database{
  26. service: service{
  27. name: "MySQL",
  28. port: 3306,
  29. },
  30. dbType: "InnoDB",
  31. dataDir: []string{"/data/mysql/data"},
  32. logDir: "/data/mysql/logs",
  33. }
  34. mysql.manager("start") // 继承了嵌套结构体的方法
  35. mysql.backup()
  36. }
  1. [root@heyingsheng day04]# go run 09-struct/main.go
  2. MySQL start!
  3. MySQL backup success!

2.3.2. 方法重写

  1. package main
  2. import "fmt"
  3. type service struct {
  4. name string
  5. port uint16
  6. }
  7. type database struct {
  8. service
  9. dbType string
  10. dataDir []string
  11. logDir string
  12. }
  13. func (s service)manager(operate string) {
  14. switch operate {
  15. case "start":
  16. fmt.Printf("%s start!\n", s.name)
  17. case "stop":
  18. fmt.Printf("%s stop!\n", s.name)
  19. }
  20. }
  21. func (d database)manager(operate string) {
  22. switch operate {
  23. case "start":
  24. fmt.Printf("%s start!\n", d.name)
  25. case "stop":
  26. fmt.Printf("%s stop!\n", d.name)
  27. case "restart":
  28. fmt.Printf("%s restart!\n", d.name)
  29. }
  30. }
  31. func main() {
  32. mysql := database{
  33. service: service{
  34. name: "MySQL",
  35. port: 3306,
  36. },
  37. dbType: "InnoDB",
  38. dataDir: []string{"/data/mysql/data"},
  39. logDir: "/data/mysql/logs",
  40. }
  41. mysql.manager("restart")
  42. }
  1. [root@heyingsheng day04]# go run 09-struct/main.go
  2. MySQL restart!

2.4. 继承和接口的区别

  • 接口和继承需要解决的问题不同:
    • 继承是将重复代码抽象出来,解决的是代码的复用性和可维护性
    • 接口在于定义需要实现的方法,让其它类型去实现这些方法,提高代码规范性
  • 接口和继承与数据类型的关系不同
    • 接口要满足的是 Like - a 的关系,不需要明确指定
    • 继承要满足的是 Is - a 的关系,需要明确指定

3. 多态

Golang中的多态是使用 Interface 来实现的,即变量或者实例具备多种形态。在Golang中多种数据类型实现了相同的接口,那么这个接口变量就具备了不同的数据形态,调用相同的方法也会因为数据类型不同就呈现了不同的结果!

3.1. 多态参数

定义一个形参为接口的函数,该函数就能接收所有实现该接口的实例,并根据实例所属类型不同,实现不同的逻辑,如下面这个简单案例所演示的,line 24 backup 函数即可接收mysql结构体实例,也可以接收redis结构体实例!再比如,一个数据查询函数接收一个接口变量完成数据查询,这个接口变量可以是 mysql ,也可以是 oracle,只要mysql和oracle实现了这个接口即可。

  1. package main
  2. import "fmt"
  3. type redis struct {
  4. name string
  5. dstPath string
  6. }
  7. func (r redis) backup() {
  8. fmt.Printf("%s 备份完毕,备份存在路径: %s\n", r.name, r.dstPath)
  9. }
  10. type mysql struct{ name string }
  11. func (m mysql) backup() {
  12. fmt.Println(m.name, "备份完毕!")
  13. }
  14. type backuper interface {
  15. backup()
  16. }
  17. func backup(b backuper) {
  18. b.backup()
  19. }
  20. func main() {
  21. redis := redis{"Redis", "/opt/backup/redis"}
  22. mysql := mysql{"MySQL"}
  23. backup(redis)
  24. backup(mysql)
  25. }

3.2. 多态数组

标题叫多态数组,其实可以是切片、数组、字段等。以多态数组为例:

  1. package main
  2. import "fmt"
  3. type Student struct {
  4. Name string
  5. Score int
  6. }
  7. type Worker struct {
  8. Name string
  9. sal float64
  10. }
  11. type Person interface {
  12. info()
  13. }
  14. func (s Student)info() {
  15. fmt.Printf("[name=%v; sal=%v]\n", s.Name, s.Score)
  16. }
  17. func (w Worker)info() {
  18. fmt.Printf("[name=%v; sal=%v]\n", w.Name, w.sal)
  19. }
  20. func main() {
  21. var s0 = [3]Person{}
  22. s0[0] = Student{"张三", 94}
  23. s0[1] = Worker{"李四", 15000}
  24. s0[2] = Student{"王五", 68}
  25. fmt.Println(s0) // [{张三 94} {李四 15000} {王五 68}]
  26. }