GO语言学习之面向对象(3)
6.面向对象编程思想-抽象
6.1抽象的介绍
我们在前面去定义一个结构体时候,实际上就是把一类事物共有的属性
(字段)和行为(方法)提取出来,形成一个物理模型(结构体)。这种研究问题的方法称为抽象
银行账户 属性/字段 账号结构体
1.账户.2.密码.3.余额
/行为/方法
1.存款2.取款.3.查询
6.2代码实现
package main
import "fmt"
//定义一个结构体
type Account struct {
AccountNo string
Pwd string
Balance float64
}
//方法
func (account *Account)Cun(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)QuKuan(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)YuE(pwd string){
//输入密码
if pwd!=account.Pwd{
fmt.Println("你输入的密码不正确。")
return
}
fmt.Printf("您的账号为%v,余额为 %v",account.AccountNo,account.Balance)
}
func main() {
//测试
account :=Account{
AccountNo: "1234567890",
Pwd: "XXOO",
Balance: 200.0,
}
account.Cun(1000000,"XXOO")
account.QuKuan(20000.0,"XXOO")
account.YuE("XXOO")
}
我们可以改成手动录入数据
7.面向对象编程三大特征-封装
7.1基本介绍
Go具有面向对象编程语言三大特征:封装,继承,多态。
只是实现的方式和其他OOP语言不一样,下面我们开始学习
7.2封装
封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其他包只有通过被授权的操作(方法),才能对字段进行操作
7.3封装的理解和好处
1》隐藏实现细节
2》可以对数据进行验证,保证安全合理(Age)
7.4如何体现封装
1》对结构体中的属性进行封装
2》通过方法,包 实现封装
7.5封装的实现步骤
1》将结构体,字段(属性)的首字母小写(不能导出了,其他包不能使用,类似private)
2》给结构体所在包提供一个工厂模式的函数,首字母大写,类似一个构造函数
3》提供一个首字母大写的Set方法(类似其他语言的public),用于对属性判断并赋值
func(var 结构体类型名) SetXxx(参数列表)(返回值列表){
//加入数据验证的业务逻辑
var .字段=参数
}
4》提供一个首字母大写的Get方法(类似其他语言的public),用于获取属性的值
func(var 结构体类型名) GetXxx(参数列表)(返回值列表){
//加入数据验证的业务逻辑
var .字段=参数
}
特别说明:
在go开发中,并没有特别强调封装,这点不像Java,所以,不要用Java的语法特性来看待go,go本身对面向对象的特性做了简化的
7.6 快速入门案例
写一个程序 (teacher.go),不能随便查看老师的年龄,工资等隐私,并对输入的年龄进行合理的验证,设计:model包(teacher.go),main包(main.go调用teacher结构体)
model/teacher.go
package model
import "fmt"
type teacher struct {
Name string
age int//其他包不能直接访问
sal float64
}
//写一个工厂模式函数,相当于构造函数
func NewTeacher(name string)*teacher{
return &teacher{
Name:name,
}
}
//为了访问age 和sal 我们编写一对 setxxx方法和 getxxX方法
func (teacher *teacher)SetAge(age int){
if age>0 && age <150{
teacher.age=age
}else {
fmt.Println("年龄范围不正确。。。")
}
}
//
func (teacher *teacher)GetAge()int{
return teacher.age
}
func (teacher *teacher)SetSal(sal float64) {
if sal>=3000&&sal<=30000{
teacher.sal=sal
}else {
fmt.Println("薪水不符合标准")
}
}
func (teacher *teacher)GetSal()float64{
return teacher.sal
}
main/main.go
package main
import (
"byteDemo07/model"
"fmt"
)
func main() {
//teacher 结构体是首字母小写,我们通过工厂模式来解决
tea:=model.NewTeacher("张老师")
tea.SetAge(28)
tea.SetSal(29999)
fmt.Println(tea)
fmt.Println(tea.Name,"age=",tea.GetAge(),"sal=",tea.GetSal())
}
7.7小练习
要求:
1》创建程序,在model包中定义Account结构体;在main函数中体会GO的封装性
2》Account的结构体要求具有字段:
账号:(长度在6–10之间)
余额:(必须>10)
密码必须六位
3》通过SetXXX的方法给Account的字段赋值
4》在main函数中进行测试
model:包
package model
import "fmt"
//定义一个结构体account
type account struct {
accountNo string
pwd string
balance float64
}
//工厂模式函数
func NewAccount(accountNo string,pwd string,balance float64)*account{
if len(accountNo)<6||len(accountNo)>10{
fmt.Println("账号不对")
return nil
}
if len(pwd)!=6{
fmt.Println("密码长度不对")
return nil
}
if balance<10{
fmt.Println("余额数目不对...")
return nil
}
return &account{
accountNo: accountNo,
pwd: pwd,
balance: balance,
}
}
//方法
//1.存款
func (account *account)Cun(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) QuKuan(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)YuE(pwd string){
//看输入密码是否正确
if pwd!=account.pwd{
fmt.Println("您输入密码不正确:")
return
}
fmt.Printf("你的账户为:%v 余额为:%v \n",account.accountNo,account.balance)
}
main包:
package main
import (
"byteDemo07/model"
"fmt"
)
func main() {
//teacher 结构体是首字母小写,我们通过工厂模式来解决
tea:=model.NewTeacher("张老师")
tea.SetAge(28)
tea.SetSal(29999)
fmt.Println(tea)
fmt.Println(tea.Name,"age=",tea.GetAge(),"sal=",tea.GetSal())
}
8.面向对象编程三大特征-继承
8.1看一个问题,引出继承的必要性
package main
import "fmt"
//编写一个学生考试系统
//小学生
type Small struct {
Name string
Age int
Score int
}
//显示他的成绩
func (s *Small)ShowInfo(){
fmt.Printf("姓名:%v,年龄:%v,成绩:%v\n",s.Name,s.Age,s.Score)
}
//
func (s *Small)SetScore(score int) {
s.Score=score
}
func (s *Small)testing() {
fmt.Println("小学生正在考试。。。")
}
//大学生,研究生
type Big struct {
Name string
Age int
Score int
}
//显示他的成绩
func (s *Big)ShowInfo(){
fmt.Printf("姓名:%v,年龄:%v,成绩:%v\n",s.Name,s.Age,s.Score)
}
//
func (s *Big)SetScore(score int) {
s.Score=score
}
func (s *Big)testing() {
fmt.Println("小学生正在考试。。。")
}
//初中生
//高中生
func main() {
var s =&Small{
Name: "tom",
Age: 10,
Score: 100,
}
s.SetScore(90)
s.ShowInfo()
s.testing()
var b =&Big{
Name: "tom",
Age: 10,
Score: 100,
}
b.SetScore(90)
b.ShowInfo()
b.testing()
}
》对上面代码小结
1》Small和 Big 两个结构体的字段和方法几乎,但是我们却写了相同的代码,代码复用性不强
2》代码冗余,而且不利于维护,同时也不利于功能的扩展
3》解决方法–通过继承方式来解决
8.2继承基本介绍和示意图
继承可以解决代码复用,让我们的编程更加靠近人类思维
当多个结构体存在相同属性(字段)和方法时,可以从这些结构体中抽象出结构体(比如Student),在该结构体中定义这些相同的属性和方法
其他的结构体不需要重新定义这些属性(字段)和方法,只需要嵌套一个Studnet匿名结构体即可
student <共有字段,共有方法)
small(自有字段,自有方法)Big(自有字段,自有方法)
在go中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现继承性
8.3嵌套匿名结构体的基本语法
type Students struct{
Name string
Age int
Score int
}
type Smalls struct{
Students//这里嵌套的就是匿名结构体Studnets
hight int
}
8.4快速入门案例
我们对8.1中代码,使用匿名结构体的方式来实现继承特性,请大家注意体会这样编程的好处
package main
import "fmt"
//编写一个学生考试系统
type Student struct {
Name string
Age int
Score int
}
//显示他的成绩
func (s *Student)ShowInfo(){
fmt.Printf("姓名:%v,年龄:%v,成绩:%v\n",s.Name,s.Age,s.Score)
}
//
func (s *Student)SetScore(score int) {
s.Score=score
}
//小学生
type Small struct {
Student
}
//小学生特有方法
func (s *Small)testing() {
fmt.Println("学生正在考试。。。")
}
//大学生
type Big struct {
Student
}
func (b *Big)testing() {
fmt.Println("大学时在考试")
}
//
func main() {
//当我们对结构体嵌入匿名结构体使用方法会发生变化
s:=&Small{}
s.Student.Name="tom"
s.Student.Age=100
s.Student.SetScore(88)
s.ShowInfo()
}
8.5继承给编程带来的便利
1》提高了代码的可维护性
2》代码的扩展性和维护性提高了
8.6继承的深入讨论
1》结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段,方法,都可以使用
func main() {
//当我们对结构体嵌入匿名结构体使用方法会发生变化
s:=&Small{}
s.Student.Name="tom"
s.Student.Age=100
s.Student.SetScore(88)
s.ShowInfo()
}
2》匿名结构体字段访问可以简化
func main() {
//当我们对结构体嵌入匿名结构体使用方法会发生变化
s:=&Small{}
s.Name="tom"
s.Age=100
s.SetScore(88)
s.ShowInfo()
}
对上面的代码总结:
(1)当我们直接通过b访问字段或方法时,其执行流程如下:s.Name
(2)编辑器会先看s对应的类型有没有Name ,如果有,则直接调用s类型的Name字段
(3)如果没有就去看B中·嵌入的匿名结构体A有没有声明Name字段,如果有就调用,如果没有就继续查找。。。如果都找不到就报错
3》当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分
s.SetScore(88)
s.Studnet.SetScore(88)
4》结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译错误
type A struct{
Name string
age int
}
type B struct{
Name string
Score int
}
type C struct{
A
B
}
func main(){
var c C
c.A.Name="lilu"//必须指定引自谁
fmt.Println(c)
}
5》如果一个struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或者方法时,必须带上结构体的名字
type D struct{
a A//有名结构体
}
//如果D中国是一个有名结构体,则访问有名结构体的字段时,就必须带上有名结构体的名字
var d D
d.a.Name="jack"
6》嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
type Goods struct {
Name string
Price float64
}
type Brand struct {
Name string
Size float64
}
type TV struct {
Goods
Brand
}
type TV2 struct {
*Goods
*Brand
}
//嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
tv:=TV{Goods{"空调",5999.0},Brand{"GELEE",3.0}}
tv2:=TV{
Goods{
Name: "冰箱",
Price: 1999.0,
},
Brand{
Name: "海尔",
Size: 5.0,
},
}
fmt.Println("tv",tv)
fmt.Println("tv2",tv2)
tv3:=TV2{&Goods{"空调2",8888.0},&Brand{"美的",1.8}}
tv4:=TV2{
&Goods{
Name:"饮水机",
Price:199.9,
},
&Brand{
Name:"美的",
Size:2.0,
},
}
fmt.Println("tv3",*tv3.Goods,*tv3.Brand)
fmt.Println("tv4",*tv4.Goods,*tv4.Brand)
8.7练习
结构体的匿名字段是基本数据类型,如何访问,下面代码输出什么
package main
import "fmt"
type Goods struct {
Name string
Price float64
}
type A struct {
Goods
int
Num int
}
func main() {
var a A
a.Name="热水器"
a.Price=18.8
a.int=200
a.Num=500
fmt.Println(a)
}
说明:
1》如果一个结构体有Int类型的匿名字段,就不能第二个
2》如果需要有多个int的字段,则必须给int字段指定名字
8.8面向对象编程-多重继承
》多重继承说明
如一个struct 嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承
》案例演示
通过一个案例来说明多重继承的使用
type Goods struct {
Name string
Price float64
}
type Brand struct {
Name string
Size float64
}
type TV struct {
Goods
Brand
}
注意:
如果嵌入的匿名结构体有相同的字段或者方法名,则在访问时,需要通过匿名结构体类型来区分:
//演示访问: Goods 的name
fmt.Println(tv.Goods.Name)
fmt.Println(tv.Price)
为了保证代码的简洁性,建议大家尽量不要使用多重继承