Go语言面向编程的思想_抽象_封装、继承

面向对象编程思想-抽象

  • 在定义结构体的时候,把一类事物的共有的属性(字段)和行为(方法)提取出来,形成一个物理模型(模板),这种研究问题的方法称为抽象
    • 在这里插入图片描述
    • 代码案例:
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)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值