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 }
```go
package main
import (
"fmt"
"studygo/day06/01-enc01/person"
)
func main() {
tom := person.NewPerson("Tom")
fmt.Printf("%T %v\n", tom, tom)
tom.SetAge(100)
fmt.Printf("%T %v\n", tom, tom.GetAge())
}
[root@heyingsheng studygo]# go run day06/01-enc01/main/main.go
*person.person &{Tom 0}
*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() }
```
[root@heyingsheng studygo]# go run day06/02-inherit/mian.go
wendy在睡觉!
wendy在说话!
玩家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中没有的字段或方法,解释器会继续向父类寻找,直到顶层还没有就报错!
package main
import "fmt"
type Person struct {
Name string
Age int
}
type Student struct {
Person
Name string
Class string
}
func (s *Student)String() string {
return fmt.Sprintf("[s.name=%v; s.Class=%v: s.Age=%v, s.Person.Name=%v, s.Person.Age=%v]",
s.Name, s.Class, s.Age, s.Person.Name, s.Person.Age)
}
func main() {
s0 := Student{
Person: Person{Name:"P-zhangsan", Age:18},
Name: "S-张三",
Class: "大一",
}
fmt.Println(&s0)
}
[root@heyingsheng studygo]# go run day06/02-inherit/no1.go
[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
。方法也是如此!
package main
import "fmt"
type A struct {
Name string
Age int
}
type B struct {
Name string
Age int
Sal float64
}
type C struct {
A
B
Name string
}
func main() {
var c C
c.Name = "张三" // c.Name
c.Sal = 10000 // c.B.Sal
//c.Age = 18 // 报错 ambiguous selector c.Age 模糊的选择器 c.Age
c.A.Age = 18
fmt.Printf("%v\n", c)
}
2.2.3. 有名结构体(组合)
上面提到的继承都是继承自匿名结构体,如果是有名结构体,那么称为组合,其实是一个特殊的继承方式。这种继承方式下,想要访问父类的方法和属性,必须要明确指定哪个父类,不可以简写!如下案例对别上面的案例分析。
package main
import "fmt"
// 生物信息
type Animal struct {
Name string
Blood int
}
// 玩家信息
type Player struct {
A 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) //p.Name undefined (type *Player has no field or method Name)
fmt.Printf("%s在说话!\n", p.A.Name)
}
func (p *Player) Info() {
fmt.Printf("玩家%s的血量:%v, San: %v,饱食度:%v\n", p.A.Name, p.A.Blood, p.San, p.Hunger)
}
func main() {
wendy := Player{
A: Animal{Name: "wendy", Blood: 150},
San: 200,
Hunger: 150,
}
wendy.A.Sleep()
wendy.Speak()
wendy.Info()
}
2.3. 方法继承和重写实现思路
2.3.1. 方法继承
package main
import "fmt"
type service struct {
name string
port uint16
}
type database struct {
service
dbType string
dataDir []string
logDir string
}
func (s service)manager(operate string) {
switch operate {
case "start":
fmt.Printf("%s start!\n", s.name)
case "stop":
fmt.Printf("%s stop!\n", s.name)
}
}
func (d database)backup() {
fmt.Printf("%s backup success!\n", d.name)
}
func main() {
mysql := database{
service: service{
name: "MySQL",
port: 3306,
},
dbType: "InnoDB",
dataDir: []string{"/data/mysql/data"},
logDir: "/data/mysql/logs",
}
mysql.manager("start") // 继承了嵌套结构体的方法
mysql.backup()
}
[root@heyingsheng day04]# go run 09-struct/main.go
MySQL start!
MySQL backup success!
2.3.2. 方法重写
package main
import "fmt"
type service struct {
name string
port uint16
}
type database struct {
service
dbType string
dataDir []string
logDir string
}
func (s service)manager(operate string) {
switch operate {
case "start":
fmt.Printf("%s start!\n", s.name)
case "stop":
fmt.Printf("%s stop!\n", s.name)
}
}
func (d database)manager(operate string) {
switch operate {
case "start":
fmt.Printf("%s start!\n", d.name)
case "stop":
fmt.Printf("%s stop!\n", d.name)
case "restart":
fmt.Printf("%s restart!\n", d.name)
}
}
func main() {
mysql := database{
service: service{
name: "MySQL",
port: 3306,
},
dbType: "InnoDB",
dataDir: []string{"/data/mysql/data"},
logDir: "/data/mysql/logs",
}
mysql.manager("restart")
}
[root@heyingsheng day04]# go run 09-struct/main.go
MySQL restart!
2.4. 继承和接口的区别
- 接口和继承需要解决的问题不同:
- 继承是将重复代码抽象出来,解决的是代码的复用性和可维护性
- 接口在于定义需要实现的方法,让其它类型去实现这些方法,提高代码规范性
- 接口和继承与数据类型的关系不同
- 接口要满足的是
Like - a
的关系,不需要明确指定 - 继承要满足的是
Is - a
的关系,需要明确指定
- 接口要满足的是
3. 多态
Golang中的多态是使用 Interface
来实现的,即变量或者实例具备多种形态。在Golang中多种数据类型实现了相同的接口,那么这个接口变量就具备了不同的数据形态,调用相同的方法也会因为数据类型不同就呈现了不同的结果!
3.1. 多态参数
定义一个形参为接口的函数,该函数就能接收所有实现该接口的实例,并根据实例所属类型不同,实现不同的逻辑,如下面这个简单案例所演示的,line 24 backup 函数即可接收mysql结构体实例,也可以接收redis结构体实例!再比如,一个数据查询函数接收一个接口变量完成数据查询,这个接口变量可以是 mysql ,也可以是 oracle,只要mysql和oracle实现了这个接口即可。
package main
import "fmt"
type redis struct {
name string
dstPath string
}
func (r redis) backup() {
fmt.Printf("%s 备份完毕,备份存在路径: %s\n", r.name, r.dstPath)
}
type mysql struct{ name string }
func (m mysql) backup() {
fmt.Println(m.name, "备份完毕!")
}
type backuper interface {
backup()
}
func backup(b backuper) {
b.backup()
}
func main() {
redis := redis{"Redis", "/opt/backup/redis"}
mysql := mysql{"MySQL"}
backup(redis)
backup(mysql)
}
3.2. 多态数组
标题叫多态数组,其实可以是切片、数组、字段等。以多态数组为例:
package main
import "fmt"
type Student struct {
Name string
Score int
}
type Worker struct {
Name string
sal float64
}
type Person interface {
info()
}
func (s Student)info() {
fmt.Printf("[name=%v; sal=%v]\n", s.Name, s.Score)
}
func (w Worker)info() {
fmt.Printf("[name=%v; sal=%v]\n", w.Name, w.sal)
}
func main() {
var s0 = [3]Person{}
s0[0] = Student{"张三", 94}
s0[1] = Worker{"李四", 15000}
s0[2] = Student{"王五", 68}
fmt.Println(s0) // [{张三 94} {李四 15000} {王五 68}]
}