golang面向对象编程思想
一、抽象
就是把一类事物的共有属性(字段)和行为(方法)提取出来,形成一个物理模板。这种研究问题的方法称为抽象。
例子:
银行账号都有卡号,密码和余额属性,且可以存款,取款,查询余额这些行为,我们将这些属性和行为提取出来,形成一个模板,用代码实现如下:
项目结构图:
account.go
package model
import "fmt"
// 银行账号
type Account struct {
AccountNo string // 卡号
Pwd string // 密码
Balance float64 // 余额
}
// 存款
func (account *Account) Deposite(money float64, pwd string) {
// 检验密码
if account.Pwd != pwd {
fmt.Println("密码不正确")
return
}
// 检验存款金额
if money < 0 {
fmt.Println("输入的金额不正确")
return
}
account.Balance = account.Balance + money
fmt.Println("存款成功")
}
// 取款
func (account *Account) WithDraw(money float64, pwd string) {
// 检验密码
if account.Pwd != pwd {
fmt.Println("密码不正确")
return
}
// 检验取款金额
if money < 0 || money > account.Balance {
fmt.Println("输入的金额不正确")
return
}
account.Balance = account.Balance - money
fmt.Println("取款成功")
}
// 查询余额
func (account *Account) Query(pwd string) {
// 检验密码
if account.Pwd != pwd {
fmt.Println("密码不正确")
return
}
fmt.Printf("你的账号%v余额为%v\n", account.AccountNo, account.Balance)
}
main.go
package main
import (
"model"
)
func main() {
account := model.Account{
AccountNo: "pf11111111",
Pwd: "666666",
Balance: 1000,
}
// 查询余额
account.Query("666666")
// 存款
account.Deposite(1000, "666666")
// 查询余额
account.Query("666666")
// 取款
account.WithDraw(200, "666666")
// 查询余额
account.Query("666666")
// 查询余额
account.Query("888888")
}
输出结果:
你的账号pf11111111余额为1000
存款成功
你的账号pf11111111余额为2000
取款成功
你的账号pf11111111余额为1800
密码不正确
二、 封装
封装就是将抽象出来的字段和对字段的操作封装在一起,数据被保护在内部,程序的其他包只有通过授权的操作(方法),才能对字段进行操作。现实中对电视机的操作就是典型的封装。
1. 实现封装的步骤
- 将结构体、字段(属性)的首字母小写(不能导出,其他包不能使用,类似private)
- 给结构体所在的包提供一个工厂模式的函数,首字母大写,类似一个构造函数
- 提供一个首字母大写的Set方法,用于对属性判断并赋值
- 提供一个首字母大写的Get方法用于获取属性的值
例子:
一个人的年龄和工资是属于隐私信息,我们如何实现封装,代码如下:
项目结构图:
person.go
package model
import "fmt"
type person struct {
Name string // 名字
age int //年龄 其他包不可用
sal float64 //工资 其他包不可用
}
// 写一个工厂模式的函数,相当于构造函数
func NewPerson(name string) *person {
return &person{
Name: name,
}
}
// 为age和sal编写一对SetXxx方法和GetXxx的方法
func (p *person) SetAge(age int) {
if age > 0 && age < 150 {
p.age = age
} else {
fmt.Println("年龄范围不正确")
}
}
func (p *person) GetAge() int {
return p.age
}
func (p *person) SetSal(sal float64) {
if sal > 0 {
p.sal = sal
} else {
fmt.Println("薪水范围不正确")
}
}
func (p *person) GetSal() float64 {
return p.sal
}
main.go
package main
import (
"fmt"
"model"
)
func main() {
p := model.NewPerson("小王")
fmt.Println("p=", *p)
p.SetAge(20)
p.SetSal(5000)
fmt.Printf("%v年龄是%v,薪水是%v\n", p.Name, p.GetAge(), p.GetSal())
}
输出结果:
p= {小王 0 0}
小王年龄是20,薪水是5000
三、继承
当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法。其他结构体不需要重新定义这些属性和方法,只需嵌套该结构体的匿名结构体即可。也就是说在Golang中,如果一个struct嵌套了另一个结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性
例子:
编写一个学生考试系统
项目结构图:
student.go
package model
import "fmt"
type Student struct {
Name string
Age int
Score float32
}
func (stu *Student) ShowInfo() {
fmt.Printf("学生名=%v 年龄=%v 成绩=%v\n", stu.Name, stu.Age, stu.Score)
}
func (stu *Student) SetScore(score float32) {
stu.Score = score
}
pupil.go
package model
import "fmt"
// 小学生
type Pupil struct {
Student // 嵌入Student匿名结构体,实现继承
}
func (pupil Pupil) Testing() {
fmt.Println("小学生正在考试中...")
}
graduate.go
package model
import "fmt"
// 大学生
type Graduate struct {
Student // 嵌入Student匿名结构体,实现继承
}
func (graduate Graduate) Testing() {
fmt.Println("大学生正在考试中...")
}
main.go
package main
import (
"model"
)
func main() {
pupil := model.Pupil{}
pupil.Name = "tom"
pupil.Age = 10
pupil.Testing()
pupil.SetScore(80)
pupil.ShowInfo()
graduate := model.Graduate{}
graduate.Name = "mary"
graduate.Age = 20
graduate.Testing()
graduate.SetScore(100)
graduate.ShowInfo()
}
输出结果:
小学生正在考试中...
学生名=tom 年龄=10 成绩=80
大学生正在考试中...
学生名=mary 年龄=20 成绩=100
1.细节
- 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分。
package main
import (
"fmt"
)
type A struct {
Name string
}
func (a *A) SayHello() {
fmt.Println("A.......SayHello", a.Name)
}
type B struct {
A
Name string
}
func (b *B) SayHello() {
fmt.Println("B.......SayHello", b.Name)
}
func main() {
b := B{}
b.Name = "tom"
b.SayHello()
b.A.SayHello()
b.A.Name = "marry"
b.A.SayHello()
}
输出结果:
B.......SayHello tom
A.......SayHello
A.......SayHello marry
- 结构体嵌入两个(或多个)匿名结构体,若两个结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错
package main
import (
"fmt"
)
type A struct {
Name string
Age int
}
type B struct {
Name string
Score float32
}
func (a *A) SayHello() {
fmt.Println("A.......SayHello", a.Name)
}
func (b *B) SayHello() {
fmt.Println("B.......SayHello", b.Name)
}
type C struct {
A
B
}
func main() {
var c C
// 如果C没有Name字段,而A和B有Name字段,这时就必须通过指定匿名结构体名字区分
// 所以c.Name就会编译错误,这个规则对方法也是一样的
// c.Name = "tom" // 编译报错
c.A.Name = "tom"
c.Age = 10
c.B.Name = "jack"
c.Score = 88.8
// c.SayHello() // 编译报错
fmt.Println("c=", c)
c.A.SayHello()
c.B.SayHello()
}
输出结果:
c= {{tom 10} {jack 88.8}}
A.......SayHello tom
B.......SayHello jack
- 如果一个结构体嵌套了一个有名结构体,这种模式是组合,如果是组合关系,那么在访问组合结构体的字段或方法时,必须带上结构体名字的
package main
import (
"fmt"
)
type A struct {
Name string
Age int
}
type B struct {
a A // 组合关系
}
func (a *A) SayHello() {
fmt.Println("A.......SayHello", a.Name)
}
func main() {
var b B
// 如果一个结构体嵌套了一个有名结构体,这种模式是组合,如果是组合关系,
// 那么在访问组合结构体的字段或方法时,必须带上结构体名字的
b.a.Name = "tom"
b.a.Age = 10
fmt.Println("b=", b)
b.a.SayHello()
}
输出结果:
b= {{tom 10}}
A.......SayHello tom
- 嵌套匿名结构体后,也可以在创建结构体变量(实例时),直接指定各个匿名结构体字段的值
package main
import (
"fmt"
)
type Goods struct {
Name string
Price float64
}
type Brand struct {
Name string
Address string
}
type TV struct {
Goods
Brand
}
func main() {
tv1 := TV{Goods{"电视机001", 6000.89}, Brand{"海尔", "山东"}}
// 嵌套匿名结构体后,也可以在创建结构体变量(实例时),直接指定各个匿名结构体字段的值
tv2 := TV{
Goods{
Name: "电视机002",
Price: 8000.89,
},
Brand{
Name: "康佳",
Address: "青岛",
},
}
fmt.Println("tv1=", tv1)
fmt.Println("tv2=", tv2)
}
输出结果:
tv1= {{电视机001 6000.89} {海尔 山东}}
tv2= {{电视机002 8000.89} {康佳 青岛}}