面向对象编程思想
面向对象编程思想-抽象
我们在定义一个结构体时,实际上就是把一类事物的共有的属性(字段)和行为(方法)七区出来,形成一个物理模型(模板)。这种研究问题的方法称为抽象。
package main
import "fmt"
type Account struct {
AccountNo string
Pwd string
Balance float64
}
func (account *Account) Deposit(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 余额 = %v ", account.AccountNo, account.Balance)
}
func main() {
//测试
account := &Account{
AccountNo: "gs111111",
Pwd: "666666",
Balance: 100.0,
}
account.Query("666666")
account.Deposit(100.0, "666666")
account.Query("666666")
}
面向对象的三大特征
基本介绍
Golang仍然有面向对象编程的继承、封装和多态的特性,只是实现的方式和其它OOP语言不同。
面向对象编程-封装
封装介绍
封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据呗保护在内部,程序的其他包只有通过被授权的操作(方法),才能对字段进行操作。
封装的理解和好处
- 隐藏现实细节
- 可以对数据进行验证,保证安全合理
如何体现封装
- 对结构体中的属性进行封装
- 通过方法、包,进行封装
封装的实现步骤
- 将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似private)
- 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
- 提供一个首字母大写的Set方法(类似其它语言的public),用于对属性判断并赋值
func (var 结构体类型名)SetXxx(参数列表)(返回值列表){
//加入数据验证的业务逻辑
var.字段=参数
} - 提供一个首字母大写的Get方法(类似其它语言的public),用于获取属性的值
func (var 结构体类型名)GetXxx(参数列表)(返回值列表){
return var.字段
}
案例:创建一个程序(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 >= 3000 && sal < 30000 {
p.sal = sal
} else {
fmt.Println("薪水范围不正确..")
//有程序员给一个默认值
}
}
func (p *person) GetSal() float64 {
return p.sal
}
package main
import (
"fmt"
"go_code/chapter11/model"
)
func main() {
p := model.NewPerson("smith")
p.SetAge(18)
p.SetSal(18000)
fmt.Println(p)
fmt.Println(p.Name)
fmt.Println(p.GetAge())
fmt.Println(p.GetSal())
}
创建程序,在model包中定义Account结构体,在main函数中体会Golang的封装性
Account结构体要求具有字段:账号(长度在6-10之间)、余额(必须>20)、密码(必须是六位数)
通过SetXxx的方法给Account的字段赋值
在main函数中测试
package model
import "fmt"
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 < 20 {
fmt.Println("余额数目不对...")
return nil
}
return &account{
accountNo: accountNo,
pwd: pwd,
balance: balance,
}
}
func (a *account) GetAccountNO() string {
return a.accountNo
}
func (a *account) SetPwd(pwd string) bool {
if len(pwd) != 6 {
fmt.Println("密码需要6位")
return false
} else {
a.pwd = pwd
return true
}
}
func (a *account) GetPwd() string {
return a.pwd
}
func (account *account) Deposit(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 余额 = %v ", account.accountNo, account.balance)
}
package main
import (
"fmt"
"go_code/demo/model"
)
func main() {
account := model.NewAccount("jzh11111", "999999", 10000.0)
if account != nil {
fmt.Println("创建成功=", account)
} else {
fmt.Println("创建失败")
}
account.SetPwd("123456")
fmt.Println(account)
account.Deposit(10, "123456")
account.Query("123456")
account.WithDraw(8000, "123456")
account.Query("123456")
}
面向对象编程-继承
继承的基本介绍
继承可以解决代码复用,让我们的编程更加接近人类思维
当多个结构体存在相同的属性(字段)和方法是,也已从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法。
其它的结构体不需要重新定义这些属性和方法,只需要嵌套一个匿名结构体即可。
也就是说:在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
}
func (stu *Student) ShowInfo() {
fmt.Printf("学生名=%v,年龄=%v,成绩=%v\n", stu.Name, stu.Age, stu.Score)
}
func (stu *Student) SetScore(score int) {
stu.Score = score
}
type Pupil struct {
Student
}
func (p *Pupil) testing() {
fmt.Println("小学生正在考试中...")
}
type Graduate struct {
Student
}
func (p *Graduate) testing() {
fmt.Println("大学生正在考试中...")
}
func main() {
//当我们对结构体嵌入了匿名结构体后,使用的方法会发生变化
pupil := &Pupil{}
pupil.Student.Name = "tom"
pupil.Student.Age = 8
pupil.testing()
pupil.Student.SetScore(80)
pupil.Student.ShowInfo()
graduate := Graduate{}
graduate.Student.Name = "jack"
graduate.Student.Age = 21
graduate.testing()
graduate.Student.SetScore(90)
graduate.Student.ShowInfo()
}
继承的深入讨论
- 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用
package main
import "fmt"
type A struct {
Name string
age int
}
func (a *A) SayOk() {
fmt.Println("A SayOk", a.Name)
}
func (a *A) hello() {
fmt.Println("A hello", a.Name)
}
type B struct {
A
}
func main() {
var b B
b.A.Name = "tom"
b.A.age = 19
b.A.SayOk()
b.A.hello()
}
- 匿名结构体字段访问可以简化
func main() {
var b B
b.Name = "tom"
b.age = 19
b.SayOk()
b.hello()
}
(1)当我们直接通过b访问字段或方法时,执行流程如下,如b.Name
(2)编译器会先看b对应的类型有没有Name,如果有,直接调用B类型的Name字段
(3)如果没有直接看B中嵌入的匿名结构体A中有没有声明Name,如果有就调用,如果没有就继续查找,如果都找不到,就报错
- 当结构体和匿名结构体有相同的字段或者方法是,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分。
- 结构体嵌入两个(或多个)匿名结构图,图两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确匿名结构体的名字,否则报错。
package main
import "fmt"
type A struct {
Name string
age int
}
type B struct {
Name string
age int
}
type C struct {
A
B
Name string
}
func main() {
var c C
//如果c没有Name字段,而A和B有Name,这时就必须通过指定匿名字段来区分
c.Name = "tom"
fmt.Println("c", c)
}
- 如果一个struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体字段或方法时,必须带上结构体的名字。
type D struct{
a A //有名结构体(组合关系)
}
func main (){
var d D
d.a.Name = "jack"
}
- 嵌套匿名结构体后,也可以创建结构体变量(实例)时,直接指定各个匿名结构体字段的值。
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() {
tv := TV{Goods{"电视机001", 5000.99}, Brand{"海尔", "山东"}}
tv2 := TV{
Goods{
Price: 5000.00,
Name: "电视机002",
},
Brand{
Name: "夏普",
Address: "北京",
},
}
fmt.Println("tv", tv)
fmt.Println("tv2", tv2)
tv3 := TV2{&Goods{"电视机003", 7000.99}, &Brand{"创维", "河南"}}
tv4 := TV2{
&Goods{
"电视机004",
8000.99,
},
&Brand{
"TCL",
"上海",
},
}
fmt.Println("tv3", *tv3.Goods, *tv3.Brand)
fmt.Println("tv4", *tv4.Goods, *tv4.Brand)
}
结构体的匿名字段是基本数据类型,如何访问
type Monster struct{
Name string
Age int
}
type E struct {
Monster
int
n int
}
func main(){
var e E
e.Name = "狐狸精"
e.Age = 300
e.int = 20
e.n = 40
fmt.Println("e = ",e)
}
说明
- 如果一个结构体有int类型的匿名字段,就不能有第二个
- 如果需要有多个int字段,则必须给int字段指定名称
多重继承
说明
如一个struct嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。
细节说明
- 如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分
- 为了保证代码的简洁性,建议尽量不要使用多重继承
接口(interface)
基本介绍
interface类型可以定义一组方法,但是这些方法不需要实现,并且interface不能包含任何变量。到某个自定义类型(比如结构体Phone)要使用时,在根据具体情况把这些方法写出来。
基本语法
type 接口名 interface{
method1(形参列表)返回值列表
method2(形参列表)返回值列表
…
}
实现接口所有方法
func(t 自定义类型)method1(形参列表)返回值列表{
//方法实现
}
func(t 自定义类型)method2(形参列表)返回值列表{
//方法实现
}
说明:
- 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法,接口体现了程序设计的多态和高内聚低耦合的思想
- Golang中的接口,不需要显示的实现,只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang中没有implement这样的关键字。
注意事项和细节
- 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)
- 接口中所有的方法都没有方法体,即都是没有实现的方法。
- 在Golang中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口。
- 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型。
- 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。
- 一个自定义类型可以实现多个接口
- Golang接口中不能有任何变量
- 一个接口,可以继承多个别的接口,这事如果要是先A接口,也必须将B,C接口的方法也全部实现。
- interface类型默认是一个指针(引用类型),如果没有对interface初始化使用,那么会输出nil
- 空接口interface{}没有任何方法,所以所有类型都实现了空接口 ,即我们可以把任何变量赋给空接口。
type T interface{
}
func main(){
var t T = stu
fmt.Println(t)
var t2 interface{} = stu
var num1 float64 = 8.8
t2 = num1
t = num1
fmt.Println(t2,t)
}
接口的实践
实现对hero结构体切片的排序 sort_Sort(data Interface)
package main
import (
"fmt"
"math/rand"
"sort"
)
type Hero struct {
Name string
Age int
}
type HeroSlice []Hero
// 实现接口
func (s HeroSlice) Len() int {
return len(s)
}
// less决定使用什么标准进行排序
func (s HeroSlice) Less(i, j int) bool {
return s[i].Age < s[j].Age
}
func (s HeroSlice) Swap(i, j int) {
//temp := s[i]
//s[i] = s[j]
//s[j] = temp
s[i],s[j] = s[j],s[i]
}
func main() {
var intSlice = []int{1, -1, 10, 7, 29}
sort.Ints(intSlice)
fmt.Println(intSlice)
//测试
var heroes HeroSlice
for i := 0; i < 10; i++ {
hero := Hero{
Name: fmt.Sprintf("英雄%d", rand.Intn(100)),
Age: rand.Intn(100),
}
heroes = append(heroes, hero)
}
for _, v := range heroes {
fmt.Println(v)
}
sort.Sort(heroes)
fmt.Println("--------排序后--------")
for _, v := range heroes {
fmt.Println(v)
}
}
- 将Student的切片,按照Score从大到小排序
package main
import (
"fmt"
"math/rand"
"sort"
)
type Student struct {
Name string
Age int
Score float64
}
type StudentSlice []Student
func (stu StudentSlice) Len() int {
return len(stu)
}
func (stu StudentSlice) Less(i, j int) bool {
return stu[i].Score > stu[j].Score
}
func (stu StudentSlice) Swap(i, j int) {
stu[i], stu[j] = stu[j], stu[i]
}
func main() {
var stu StudentSlice
for i := 0; i < 10; i++ {
student := Student{
Name: fmt.Sprintf("学生的名字%d", rand.Intn(100)),
Age: rand.Intn(100),
Score: float64(rand.Intn(100)),
}
stu = append(stu, student)
}
for _, v := range stu {
fmt.Println(v)
}
sort.Sort(stu)
fmt.Println("------排序后------")
for _, v := range stu {
fmt.Println(v)
}
}
实现接口与继承
package main
import "fmt"
// Monkey结构体
type Monkey struct {
Name string
}
// 声明接口
type BirdAble interface {
Flying()
}
type FishAble interface {
Swimming()
}
func (this *Monkey) climbing() {
fmt.Println(this.Name, "生来会爬树...")
}
// littleMonkey 结构体
type littleMonkey struct {
Monkey
}
// 让littleMonkey实现BirdAble
func (this *littleMonkey) Flying() {
fmt.Println(this.Name, "通过学习,会飞了...")
}
func (this *littleMonkey) Swimming() {
fmt.Println(this.Name, "通过学习,会游泳了...")
}
func main() {
monkey := littleMonkey{
Monkey{
Name: "孙悟空",
},
}
monkey.climbing()
monkey.Flying()
}
- 总结
- 当A结构体继承了B结构体,那么A结构就自动的继承了B结构体的字段和方法,并且可以直接使用
- 当A结构体需要扩展功能,同时不希望去破坏继承关系,则可以去实现某个接口即可,因此我们可以认为,实现接口是对继承机制的补充。
- 实现接口可以看作是对继承的一种补充
- 接口和继承解决的问题不同
继承的价值主要在于:解决代码的复用性和可维护性
接口的价值主要在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法 - 接口比继承更加灵活
接口比继承更加灵活,继承是满足is-a的关系,而接口只需满足like-a的关系。 - 接口在一定程度上实现代码解耦
面向对象编程-多态
基本介绍
变量(实例)具有多种形态。面向对象的第三大特征,在Go语言,多态特征是通过接口实现的,可以按照统一的接口来调用不用的实现,这时接口变量就呈现不同的形态。
package main
import "fmt"
//声明一个接口
type Usb interface {
//声明两个没有实现的方法
Start()
Stop()
}
type Phone struct {
}
//让结构体实现Usb接口的方法
func (p Phone) Start() {
fmt.Println("手机开始工作")
}
func (p Phone) Stop() {
fmt.Println("手机停止工作")
}
type Camera struct {
}
//让Camera实现usb接口的方法
func (c Camera) Start() {
fmt.Println("相机开始工作")
}
func (c Camera) Stop() {
fmt.Println("相机停止工作")
}
type Computer struct {
}
//Working方法接收一个Usb接口类型变来给你
//主要是实现Usb接口(所谓实现Usb接口,就是值实现了Usb接口声明的所有方法)
func (c Computer) Working(usb Usb) {
//通过usb接口变量来调用start和atop方法
usb.Start()
usb.Stop()
}
func main() {
//创建结构体变量
computer := Computer{}
phone := Phone{}
camera := Camera{}
computer.Working(phone)
computer.Working(camera)
}
接口体现多态的两种特征
- 多态参数
在Usb接口案例中,Usb usb,即可以接收手机变量,又可以接收相机变量,就体现了Usb接口多态 - 多态数组
演示一个案例:给Usb数组中,存放Phone结构体和Camera结构体变量,Phone还有一个特有的方法call(),请遍历Usb数组,如果是Phone变量,除了调用Usb接口声明的方法外,还需要调用Phone特有方法call。
package main
import "fmt"
type Usb interface {
//声明两个没有实现的方法
Start()
Stop()
}
type Phone struct {
name string
}
// 让结构体实现Usb接口的方法
func (p Phone) Start() {
fmt.Println("手机开始工作")
}
func (p Phone) Stop() {
fmt.Println("手机停止工作")
}
type Camera struct {
name string
}
// 让Camera实现usb接口的方法
func (c Camera) Start() {
fmt.Println("相机开始工作")
}
func (c Camera) Stop() {
fmt.Println("相机停止工作")
}
func main() {
//定义一个Usb接口数组,可以存放Phone和Camera的结构体变量
//这里体现出多态数组
var usbArr [3]Usb
usbArr[0] = Phone{"vivo"}
usbArr[1] = Phone{"小米"}
usbArr[2] = Camera{"尼康"}
fmt.Println(usbArr)
}
类型断言
基本介绍
类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言:
package main
import "fmt"
type Point struct {
x int
y int
}
func main() {
var a interface{}
var point Point = Point{1, 2}
a = point
var b Point
b = a.(Point)
fmt.Println(b)
var x interface{}
var b2 float32 = 1.1
x = b2 //空接口可以接收任意类型
//x=>float32
y := x.(float32)
fmt.Printf("y的类型是%T 值是 = %v", y, y)
}
- 对上面代码的说明:
在进行类型断言时,如果类型不匹配会报paic,因此进行类型断言时,要确保原来的空接口指向的就是要断言的类型。 - 如何在进行短延时,带上监测机制,如果成果就OK,否则也不报panic
//带检测的类型断言
var x interface{}
var b2 float32 = 1.1
x = b2 //空接口可以接收任意类型
//x=>float32
if y, ok := x.(float32); ok {
fmt.Println("convert success")
fmt.Printf("y的类型是%T 值是 = %v\n", y, y)
} else {
fmt.Println("convert fail")
}
fmt.Println("继续执行...")
package main
import "fmt"
type Usb interface {
//声明两个没有实现的方法
Start()
Stop()
}
type Phone struct {
name string
}
// 让结构体实现Usb接口的方法
func (p Phone) Start() {
fmt.Println("手机开始工作")
}
func (p Phone) Stop() {
fmt.Println("手机停止工作")
}
func (p Phone) Call() {
fmt.Println("手机开始打电话..")
}
type Camera struct {
name string
}
// 让Camera实现usb接口的方法
func (c Camera) Start() {
fmt.Println("相机开始工作")
}
func (c Camera) Stop() {
fmt.Println("相机停止工作")
}
type Computer struct {
}
func (computer Computer) Working(usb Usb) {
usb.Start()
//usb是指向Phone结构体变量,调用call方法
//类型断言
if phone, ok := usb.(Phone); ok {
phone.Call()
}
usb.Stop()
}
func main() {
//定义一个Usb接口数组,可以存放Phone和Camera的结构体变量
//这里体现出多态数组
var usbArr [3]Usb
usbArr[0] = Phone{"vivo"}
usbArr[1] = Phone{"小米"}
usbArr[2] = Camera{"尼康"}
//遍历usbArr
var computer Computer
for _, v := range usbArr {
computer.Working(v)
fmt.Println()
}
}
- 写一个函数,循环判断传入参数的类型
package main
import (
"fmt"
)
// 编写一个函数,可以判断输入的参数是什么类型
func TypeJudge(items ...interface{}) {
for index, x := range items {
switch x.(type) {
case bool:
fmt.Printf("第%v个参数是bool类型,值是%v\n", index, x)
case float32:
fmt.Printf("第%v个参数是float32类型,值是%v\n", index, x)
case float64:
fmt.Printf("第%v个参数是float64类型,值是%v\n", index, x)
case int, int32, int64:
fmt.Printf("第%v个参数是整数类型,值是%v\n", index, x)
case string:
fmt.Printf("第%v个参数是bool类型,值是%v\n", index, x)
default:
fmt.Printf("第%v个参数类型不支持,值是%v\n", index, x)
}
}
}
func main() {
var n1 float32 = 1.1
var n2 float64 = 2.3
var n3 int32 = 30
var name string = "Tyler Bennett"
address := "北京"
n4 := 300
TypeJudge(n1, n2, n3, name, address, n4)
}
增加判断Student类型和*Student类型
package main
import (
"fmt"
)
// 编写一个函数,可以判断输入的参数是什么类型
func TypeJudge(items ...interface{}) {
for index, x := range items {
switch x.(type) {
case bool:
fmt.Printf("第%v个参数是bool类型,值是%v\n", index, x)
case float32:
fmt.Printf("第%v个参数是float32类型,值是%v\n", index, x)
case float64:
fmt.Printf("第%v个参数是float64类型,值是%v\n", index, x)
case int, int32, int64:
fmt.Printf("第%v个参数是整数类型,值是%v\n", index, x)
case string:
fmt.Printf("第%v个参数是bool类型,值是%v\n", index, x)
case Student:
fmt.Printf("第%v个参数是Student类型,值是%v\n", index, x)
case *Student:
fmt.Printf("第%v个参数是*Student类型,值是%v\n", index, x)
default:
fmt.Printf("第%v个参数类型不支持,值是%v\n", index, x)
}
}
}
type Student struct {
}
func main() {
var n1 float32 = 1.1
var n2 float64 = 2.3
var n3 int32 = 30
var name string = "Tyler Bennett"
address := "北京"
n4 := 300
stu1 := Student{}
stu2 := &Student{}
TypeJudge(n1, n2, n3, name, address, n4, stu1, stu2)
}