面向对象编程思想-抽象
在定义结构体的时候,把一类事物的共有的属性(字段)和行为(方法)提取出来,形成一个物理模型(模板),这种研究问题的方法称为抽象
代码案例:
package main
import "fmt"
type Account struct{
AccountNo string
Pwd string
Balance float64
}
//存款
func (account *Account)Deposite(money float64, pwd string){
//先比对密码,输入的密码正确,才能存款
if pwd != account.Pwd{
fmt.Println("你输入的密码不正确")
return
}
//看存款金额是否正确
if money <=0{
fmt.Println("你输入的金额不正确")
return
}
account.Balance += money
fmt.Println("存款成功~~")
}
//取款
func (account *Account)WithDraw(money float64, pwd string){
//先比对密码,输入的密码正确,才能取款
if pwd != account.Pwd{
fmt.Println("你输入的密码不正确")
return
}
//看取款金额是否正确
if money <=0 || money > account.Balance{
fmt.Println("你输入的金额不正确")
return
}
account.Balance -= money
fmt.Println("取款成功~~")
}
//查询余额
func (account *Account)Query(pwd string){
//先比对密码,输入的密码正确,才能查询
if pwd != account.Pwd{
fmt.Println("你输入的密码不正确")
return
}
//输出查询信息
fmt.Printf("你的账号为:%v\t你的余额为:%v\n", account.AccountNo, account.Balance)
}
func main(){
//测试
account := &Account{
AccountNo : "gs123456",
Pwd : "6666666",
Balance: 100,
}
account.Query("6666666")
account.Deposite(5000, "6666666")
account.Query("6666666")
account.WithDraw(500, "6666666")
account.Query("6666666")
}
课后作业:增加一个控制台菜单,可以让用户动态的输入命令和选项
面向对象编程三大特征
Golang仍然有面向对象编程的继承、封装和多态的特性,只是实现的方式和其他OOP语言不一
封装
把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其他包只有通过被授权的操作(方法),才能对字段进行操作。 封装的优势
封装实现
封装实现的步骤:
将结构体、字段(属性)的首字母小写(不能导出、其他包不能使用) 给结构体所在包提供一个工厂模式的函数,首字母大写,类似一个构造函数 提供一个首字母大写的Set方法(类似其他语言的public),用于对属性判断并赋值
func (var 结构体类型名)SetXxx(参数列表)(返回值列表){
//加入数据验证的业务逻辑
var.字段 = 参数
}
提供一个首字母大写的Get方法(类似其他语言的public),用于获取属性的值
func (var 结构体类型名)GetXxx(参数列表)(返回值列表){
//加入数据验证的业务逻辑
return var.字段
}
快速入门案例
请大家看一个程序(person.go)。不能随便查看人的年龄、工资等隐私,并对输入的年龄进行合理的验证。 设计:model包(person.go),main包调用Person结构体 写了一个model包,结构体person
package model
import "fmt"
type person struct{
Name string
age int //其他包不能直接访问
salary float64 //其他包不能直接访问
}
//写一个工厂模式的函数
func Newperson(name string)*person{
return &person{
Name : name,
}
}
//为了访问age 和 salary 我们编写一对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(salary float64){
if salary > 3000 && salary < 30000{
p.salary = salary
}else{
fmt.Println("薪水范围不正确...")
//有的程序员给个默认值
}
}
func (p *person)GetSal()float64{
return p.salary
}
写了一个main包,生成一个person实例
练习
创建程序,在model包中定义Account结构体:在main函数中体会Golang的封装性。
Account结构体要求具有字段:账号(长度在6-10之间)、余额(必须>20)、密码(必须是六位) 通过SetXxx的方法给Account的字段赋值 在main函数中测试
//在model包中创建account结构体
package model
import "fmt"
type account struct{
accountNum string
balance float64
password string
}
//工厂模式
func NewAccount(accountnum string, balance float64, password string)*account{
if len(accountnum) < 6 || len(accountnum) > 10{
fmt.Println("账户长度不符合规范,账号长度应该在6-10之间")
return nil
}
if len(password) != 6{
fmt.Println("密码长度不符合规范,账号长度应该为6")
return nil
}
if balance < 20{
fmt.Println("余额不符合规范")
return nil
}
return &account{
accountNum: accountnum,
password: password,
balance: balance,
}
}
func (a *account)SetAccount(number string){
if len(number) < 6 || len(number) > 10{
fmt.Println("账户长度不符合规范,账号长度应该在6-10之间")
}
a.accountNum = number
fmt.Println("设置账户成功")
}
func (a *account)SetPwd(password string){
if len(password) != 6{
fmt.Println("输入的密码长度不准确,密码长度必须为6位")
}
a.password = password
}
//main包
package main
import (
"fmt"
"go_code/project02/exercise01/model"
)
func main(){
acc := model.NewAccount("jzh34567", 30, "234345")
if acc != nil{
fmt.Println("创建成功=", acc)
}else{
fmt.Println("创建失败")
}
acc.SetAccount("zhx34567")
fmt.Println(*acc)
acc.GetAccount()
}
继承
package main
import "fmt"
//编写一个学生考试系统
//小学生
type Pupil struct{
Name string
Age int
Score int
}
//显示他的成绩
func (p *Pupil)ShowInfo(){
fmt.Printf("学生名字:%v, 他的年龄:%v, 他的成绩:%v\n", p.Name, p.Age, p.Score)
}
func (p *Pupil)SetScore(score int){
//业务判断
p.Score = score
}
func (p *Pupil)testing(){
fmt.Println("小学生正在考试中......")
}
//除了小学生考试,还有大学生、研究生考试
//大学生
type Graduate struct{
Name string
Age int
Score int
}
//显示他的成绩
func (p *Graduate)ShowInfo(){
fmt.Printf("学生名字:%v, 他的年龄:%v, 他的成绩:%v\n", p.Name, p.Age, p.Score)
}
func (p *Graduate)SetScore(score int){
//业务判断
p.Score = score
}
func (p *Graduate)testing(){
fmt.Println("大学生正在考试中......")
}
func main(){
//测试
var pupil = &Pupil{
Name: "tom",
Age: 18,
}
pupil.testing()
pupil.SetScore(89)
pupil.ShowInfo()
}
上述代码的问题:Pupil和Graduate 两个结构体的字段和方法几乎一样 ,但是我们却写了相同的代码,代码复用性不强。 出现代码冗余,而且代码 不利于维护 ,同时也不利于功能的扩展 上述问题的解决方法:通过继承方式来解决
继承基本介绍
继承介绍
继承可以解决代码复用 当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法 其他结构体不需要重新定义这些属性(字段)和方法,只需要嵌套一个Student匿名结构体即可 即:在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性 嵌套匿名结构体的基本语法
type Goods struct{
Name string
Price int
}
type Book struct{
Goods //这里就是嵌套匿名结构体 Goods
Writer string
}
package main
import "fmt"
//编写一个学生考试系统
//小学生
type Student struct{
Name string
Age int
Score int
}
//将 Pupil 和 Graduate 共有的方法也绑定到Student类型
func(student *Student)ShowInfo(){
fmt.Printf("学生名字:%v, 他的年龄:%v, 他的成绩:%v\n", student.Name, student.Age, student.Score)
}
func (student *Student)SetScore(score int){
//业务判断
student.Score = score
}
type Pupil struct{
Student //嵌入了Student匿名结构体
}
type Graduate struct{
Student
}
//是 Pupil 特有的方法,保留
func (p *Pupil)testing(){
fmt.Println("小学生正在考试中......")
}
//是 PGraduate 特有的方法,保留
func (p *Graduate)testing(){
fmt.Println("大学生正在考试中......")
}
func main(){
//当对结构体嵌入了匿名结构体后,使用的方法会发生变化
pupil := &Pupil{}
pupil.Student.Name = "tom"
pupil.Student.Age = 12
pupil.testing()
pupil.Student.SetScore(79)
pupil.Student.ShowInfo()
graduate := &Graduate{}
graduate.Student.Name = "mary"
graduate.Student.Age = 21
graduate.testing()
graduate.Student.SetScore(89)
graduate.Student.ShowInfo()
}
继承深入讨论
结构体可以使用嵌套匿名结构体所有的字段和方法 ,即:首字母大写或者小写的字段、方法,都可以使用。 匿名结构体字段访问可以简化 (执行流程:编译器先会看 b 对应的类型有没有 Name,如果有,则直接调用B类型的Name字段,如果没有,就去看B中嵌入的匿名结构体 A 有没有声明Name字段,如果有就调用,如果没有继续查找,如果都找不到就报错) 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问 ,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分 结构体嵌入两个(或多个)匿名结构体,如果两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字 ,否则编译报错
package main
import "fmt"
type A struct{
Name string
age int
}
type B struct{
Name string
score float64
}
type C struct{
A
B
}
func main(){
var c C
//如果 c 没有Name字段,而A 和B 有Name,这时就必须通过指定匿名结构体名字来区分
//所以 c.Name 就会报编译器错误,这个规则对方法也是一样
c.A.Name = "tom"
fmt.Println(c)
}
如果一个struct嵌套了一个有名结构体,这种模式就是组合 ,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
package main
import "fmt"
type Goods struct{
Name string
Price float64
}
type Brand struct{
Name string
Address string
}
type TV struct{
Goods
Brand
}
type TV2 struct{
*Goods
*Brand
}
func main(){
v1 := TV{Goods{"电视机001", 5000.99}, Brand{"海尔", "山东"}}
v2 := TV{
Goods{
Price: 5000,
Name: "电视机002",
},
Brand{
Name: "海尔",
Address: "北京",
}}
fmt.Println("v1", v1)
fmt.Println("v2", v2)
v3 := TV2{&Goods{"电视机003", 2000}, &Brand{"创维", "陕西"}}
v4 := TV2{
&Goods{
Price: 29999,
Name: "电视机004",
},
&Brand{
Name: "海尔",
Address: "深圳",
}}
fmt.Println("v3", *v3.Goods, *v3.Brand)
fmt.Println("v4", *v4.Goods, *v4.Brand)
}
**注意:**结构体的匿名字段是基本数据类型,其访问如下;如果一个结构体有int类型的匿名字段,就不能有第二个;如果需要有多个int字段,必须给int取名字
多重继承
如一个struct嵌套了多个匿名结构体 ,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承 为了保证代码的简洁性,建议大家尽量不使用多重继承
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(){
v1 := TV{Goods{"电视机001", 5000.99}, Brand{"海尔", "山东"}}
v2 := TV{
Goods{
Price: 5000,
Name: "电视机002",
},
Brand{
Name: "海尔",
Address: "北京",
}}
fmt.Println("v1.Goods.Name=", v1.Goods.Name, "v1.Price=", v1.Price)
fmt.Println("v2.Goods.Name=", v2.Goods.Name,"v2.Brand.Name=", v2.Brand.Name, "v2.Address=", v2.Address)
}