抽象
我们在前面去定义一个结构体时候,实际上就是把一类事物的共有的属性和行为(方法)提取出来,形成—个物理模型(模板)。这种研究问题的方法称为抽象。
案例:
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.Println("你的余额为", account.Balance)
}
func main() {
account := Account{
AccountNo: "工商",
Pwd: "123",
Balance: 100.0,
}
account.Query("123")
account.Deposite(200.0, "123")
account.Query("123")
account.WithDraw(150.0, "123")
account.Query("123")
}
面向对象编程三大特征
Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP语言不一样,Golang的三大特性是如何实现的。
封装
封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作。
封装的好处:
- 隐藏实现细节
- 可以对数据进行验证,保证安全合理
如何体现封装
- 对结构体中的属性进行封装
- 通过方法,包 实现封装
封装实现步骤
-
将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似private)
-
给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
-
提供一个首字母天写的set方法(类似其它语言的public),用于对属性判断并赋值
func (var结构体类型名)SetXxx(参数列表)(返回值列表){
//加入数据验证的业务逻辑
var.字段=参数
}
-
提供一个首字母大写的Get方法(类似其它语言的public),用于获取属性的值
func (var结构体类型名)GetXxx(){
return var.字段;
}
特别说明:
在Golang开发中并没有特别强调封装,这点并不像Java,不用总是用java的语法特性来看待Golang,Golang本身对面向对象的特性做了简化的.
package model
import "fmt"
type person struct {
Name string
age int
sal float64
}
//工程模式的函数,相当与构造函数
func NewPerson(name string) *person {
return &person{
Name: name,
}
}
//为了访问age 和 sal 我们编写 一对set 和 get 方法
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/project01/main/encapsulate/model"
)
func main() {
p := model.NewPerson("SIMITH")
p.SetAge(18)
p.SetSal(5000)
fmt.Println(p.Name, p.GetAge(), p.GetSal())
}
继承
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("小学生正在考试....")
}
func main() {
var pupil = &Pupil{
Name: "tom",
Age: 12,
}
pupil.testing()
pupil.SetScore(90)
pupil.ShowInfo()
}
在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。
基本语法
package main
import "fmt"
//编写一个学生考试系统
type Student struct {
Name string
Age int
Score int
}
//将Pupil 和 Graduate 共有的方法 也绑定到 8student
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 //嵌入匿名结构体
}
//大学生
type Graduate struct {
Student
}
func (p *Pupil) testing() {
fmt.Println("小学生正在考试....")
}
func (p *Graduate) testing() {
fmt.Println("大学生正在考试....")
}
func main() {
pupil := &Pupil{}
pupil.Student.Name = "tom"
pupil.Student.Age = 8
pupil.testing()
pupil.Student.SetScore(90)
pupil.Student.showInfo()
}
优越性
- 代码的复用性提高了
- 代码的扩展性和维护性提高了
继承的深入讨论
-
结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用。
-
著名结构体字段访问可以简化
(1)当我们直接通过b访问字段或方法时,其执行流程如下比如b.Name
(2)编译器会先看b对应的类型有没有Name,如果有,则直接调用B类型的 Name字段
(3)如果没有就去看B中嵌入的匿名结构体A有没有声明Name字段,如果有就调用,如果没有继续查找.如果都找不到就报错.
-
当结构体和匿名结构体有相同的字段或者方法时编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分
-
结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。
-
如果一个struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字
-
嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值。
结构体中的匿名字段
多重继承
如一个struct嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。
多重继承的细节
如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分。
为了保证代码的简洁性,建议大家尽量不使用多重继承
接口
接口的快速入门:
package main
import "fmt"
//声明一个接口
type Usb interface {
Start()
Stop()
}
type Phone struct {
}
type Camera struct {
}
//让Phone 实现 Usb 接口的方法
func (p Phone) Start() {
fmt.Println("手机开始工作...")
}
func (p Phone) Stop() {
fmt.Println("手机停止工作...")
}
//让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 和 stop 方法
usb.Start()
usb.Stop()
}
func main() {
computer := Computer{}
phone := Phone{}
camera := Camera{}
computer.Working(phone)
computer.Working(camera)
}
基本介绍
interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。到某个自定义类型(比如结构体Phone)要使用的时候,在根据具体情况把这些方法写出来(实现)。
接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚纸偶合的思想。
Golang中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang中没有implement这样的关键字
package main
import "fmt"
//声明一个接口
type Usb interface {
Start()
Stop()
}
type Phone struct {
}
type Camera struct {
}
//让Phone 实现 Usb 接口的方法
func (p Phone) Start() {
fmt.Println("手机开始工作...")
}
func (p Phone) Stop() {
fmt.Println("手机停止工作...")
}
//让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 和 stop 方法
usb.Start()
usb.Stop()
}
func main() {
computer := Computer{}
phone := Phone{}
camera := Camera{}
computer.Working(phone)
computer.Working(camera)
}
注意事项
- 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)
- 接口中所有的方法都没有方法体,即都是没有实现的方法。
- 在Golang中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口。
- 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型。
- 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。
- 一个自定义类型可以实现多个接口
- Golang接口中不能有任何变量
- 一个接口(比如A接口)可以继承多个别的接口(比如B,C接口),这时如果要实现A接口,也必须将B,C接口的方法也全部实现.
- interface类型默认是一个指针(引用类型,如果没有对interface初始化就使用那么会输出nil
- 接口interfacel沿有任何方法,所以所右类型都实现了容接口
接口最佳实践
实现对Hero结构体切片的排序: sort.Sort(data Interface)
package main
import (
"fmt"
"math/rand"
"sort"
)
//实现对Hero结构体切片的排序: sort.Sort(data Interface)
//1.声明Hero结构体
type Hero struct {
Name string
Age int
}
//2.声明一个Hero结构体切片类型
type HeroSlice []Hero
//3.实现Interface接口
func (hs HeroSlice) Len() int {
return len(hs)
}
//Less方法就是决定你使用什么标准进行排序
//1.按Hero的年龄从小到大排序
func (hs HeroSlice) Less(i, j int) bool {
return hs[i].Age < hs[j].Age
}
func (hs HeroSlice) Swap(i, j int) {
hs[i], hs[j] = hs[j], hs[i]
}
func main() {
//先定义一个数组\切片
var inslice = []int{1, -1, 10, 7, 90}
//要求对 intslice 切片进行排序
//1.冒泡排序
//2.也可以使用系统提供的方法
sort.Ints(inslice)
fmt.Println(inslice)
//对结构体切片进行排序
//1.冒泡排序
//2.也可以使用系统提供的方法
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)
}
fmt.Println()
//调用sort.Sort
sort.Sort(heroes)
//排序后的顺序
for _, v := range heroes {
fmt.Println(v)
}
}
接口和继承的比较
package main
import "fmt"
//Monkey结构体
type Monkey struct {
Name string
}
func (this *Monkey) climbing() {
fmt.Println(this.Name, "生来会爬树..")
}
//LittleMonkey结构体
type LittleMonkey struct {
Monkey
}
//声明接口
type BirdAble interface {
Flying()
}
type FishAble interface {
Swimming()
}
//让LittleMonkey实现BirdAble
func (this LittleMonkey) Flying() {
fmt.Println(this.Name, "通过学习会飞翔...")
}
//让LittleMonkey实现FishAble
func (this LittleMonkey) Swimming() {
fmt.Println(this.Name, "通过学习会游泳...")
}
func main() {
//创建一个LittleMonkey
monkey := LittleMonkey{
Monkey{
Name: "悟空",
},
}
monkey.climbing()
monkey.Flying()
monkey.Swimming()
}
接口和继承解决的解决的问题不同
继承的价值主要在于:
解决代码的复用性和可维护性。
接口的价值主要在于:
设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。
接口比继承更加灵活
接口比继承更加灵活,继承是满足 is - a 的关系,而接口只需满足 like - a 的关系。
接口在一定程度上实现代码解耦
多态
变量(实例)具有多种形态。面向对象的第三大特征,在Go语言,多态特征是通过接口实现的。可以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。
接口体现多态特征
多态参数
在前面的Usb接口案例,Usb usb,即可以接收手机变量,又可以接收相机变量,就体现了Usb接口多态
多态数组
演示一个案例:给Usb数组中,存放Phone 结构体和Camera结构体变量,Phone还有一个特有的方法call(),请遍历Usb数组,如果是Phone变量,除了调用Usb接口声明的方法外,还需要调用Phone特有方法call.---->类型断言
类型断言
package main
import "fmt"
type Point struct {
x int
y int
}
func main() {
var a interface{}
var point Point = Point{1, 2}
a = point //ok
//如何将 a 赋给一个Point 变量?
var b Point
//b = a //error
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", y)
在进行类型断言时,如果类型不匹配就会报panic,因此进行类型断言时,要确保原来的空接口指向的就是断言的类型。
断言时带上检测机制
如何在进行断言时,带上检测机制,如果成功就 ok,否则也不要报panic
//类型断言的其他案例
var x interface{}
var b2 float32 = 1.1
x = b2 //空接口,可以接受任意类型
//x = > float32 使用类型断言
y, ok := x.(float64)
if ok == true {
fmt.Println("成功")
fmt.Printf("y的类型是%T", y)
} else {
fmt.Println("失败")
}
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个参数是 string 类型,值是%v \n", index, x)
default:
fmt.Println("类型不确定")
}
}
}
func main() {
var n1 float32 = 1.1
var n2 float64 = 2.3
var n3 int = 10
var name string = "tom"
address := "北京"
n4 := 300
TypeJudge(n1, n2, n3, n4, name, address)
}
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个参数是 string 类型,值是%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.Println("类型不确定")
}
}
}
type Student struct {
}
func main() {
var n1 float32 = 1.1
var n2 float64 = 2.3
var n3 int = 10
var name string = "tom"
address := "北京"
n4 := 300
stu1 := Student{}
stu2 := &Student{}
TypeJudge(n1, n2, n3, n4, name, address, stu1, stu2)
}