Golang 语言面向对象编程(下)
面向对象编程思想-抽象
如下代码:
package main import ( "fmt" ) //定义一个结构体Account type Account struct { AccountNo string Pwd string Balance float64 } //方法 //1. 存款 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 余额=%v \n", account.AccountNo, account.Balance) } func main() { //测试一把 account := Account{ AccountNo : "gs1111111", Pwd : "666666", Balance : 100.0, } //这里可以做的更加灵活,就是让用户通过控制台来输入命令... //菜单.... account.Query("666666") account.Deposite(200.0, "666666") account.Query("666666") account.WithDraw(150.0, "666666") account.Query("666666") }
基本介绍
1、隐藏实现细节
2、提可以对数据进行验证,保证安全合理(Age)
1、对结构体中的属性进行封装
1、将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似 private)
2、给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
3、提供一个首字母大写的 Set 方法(类似其它语言的 public),用于对属性判断并赋值
func (var 结构体类型名) SetXxx(参数列表){ //加入数据验证的业务逻辑 var.字段 = 参数 }
4、提供一个首字母大写的 Get 方法(类似其它语言的 public),用于获取属性的值
func (var 结构体类型名) GetXxx() (返回值列表) { return var.age; }
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" mdoel "go_code/test/models" ) func main() { p := mdoel.NewPerson("bingle") p.SetAge(18) p.SetSal(1800) fmt.Println(p) fmt.Println(p.Name, " age =", p.GetAge(), " sal = ", p.GetSal()) }
也就是说:在 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 (stu *Student) ShowInfo() { fmt.Printf("学生名=%v 年龄=%v 成绩=%v\n", stu.Name, stu.Age, stu.Score) } func (stu *Student) SetScore(score int) { //业务判断 stu.Score = score } //给 *Student 增加一个方法,那么 Pupil 和 Graduate都可以使用该方法 func (stu *Student) GetSum(n1 int, n2 int) int { return n1 + n2 } //小学生 type Pupil struct { Student //嵌入了Student匿名结构体 } //这时Pupil结构体特有的方法,保留 func (p *Pupil) testing() { fmt.Println("小学生正在考试中.....") } //大学生 type Graduate struct { Student //嵌入了Student匿名结构体 } //显示他的成绩 //这时Graduate结构体特有的方法,保留 func (p *Graduate) testing() { fmt.Println("大学生正在考试中.....") } func main() { //当我们对结构体嵌入了匿名结构体使用方法会发生变化 pupil := &Pupil{} pupil.Student.Name = "bingle-pupil~" pupil.Student.Age = 8 pupil.testing() pupil.Student.SetScore(70) pupil.Student.ShowInfo() fmt.Println("res=", pupil.Student.GetSum(1, 2)) graduate := &Graduate{} graduate.Student.Name = "bingle-graduate~" graduate.Student.Age = 18 graduate.testing() graduate.Student.SetScore(90) graduate.Student.ShowInfo() fmt.Println("res=", graduate.Student.GetSum(10, 20)) }
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 Name string } func (b *B) SayOk() { fmt.Println("B SayOk", b.Name) } func main() { var b B b.A.Name = "bingle" b.A.age = 19 b.A.SayOk() b.A.hello() }
2、匿名结构体字段访问可以简化,如下
func main() { var b B // b.A.Name = "bingle" // b.A.age = 19 // b.A.SayOk() // b.A.hello() //上面的写法可以简化 b.Name = "bingle" b.age = 20 b.SayOk() b.hello() }
2、编译器会先看 b 对应的类型有没有 Name,如果有,则直接调用 B 类型的 Name 字段
3、如果没有就去看 B 中嵌入的匿名结构体 A有没有声明 Name 字段,如果有就调用,如果没有继续查找..如果都找不到就报错
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) } func (a *A) say() { fmt.Println("A say", a.Name) } func (a *A) Hello() { fmt.Println("A Hello", a.Name) } type B struct { A Name string Age int } func (b *B) SayOk() { fmt.Println("B SayOk", b.Name) } func (b *B) say() { fmt.Println("B say", b.Name) } func (b *B) Hello() { fmt.Println("B Hello", b.Name) } func main() { var b B b.Name = "bingle-b" // 这时就近原则,会访问B结构体的Name字段 b.A.Name = "bingle-b-a" // b.A.Name 就明确指定访问 A 匿名结构体的Name字段 b.Age = 18 b.say() // 这时就近原则,会访问B结构体的say函数 b.Hello() b.A.Hello() // b.A.Hello() 就明确执行访问 A 匿名结构体的Hello函数 }
4、结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。
代码如下:
package main import "fmt" type A struct { Name string age int } type B struct { Name string Score float64 } type C struct { A B // Name string } func main() { var c C // 如果 c 没有Name字段,而A 和 B 有Name,这时就必须通过指定匿名结构体名字来区分 // 所以 c.Name 就会抱编译错误,这个规则对方法也是一样的 c.A.Name="bingle" fmt.Println(c) }
5、如果一个 struct 嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字
type D struct { a A // 有名结构体 组合关系 } func main() { // 如果D 中有一个有名结构体,则访问有名结构体的字段时,就必须要带上有名结构体的名字 // 比如d.a.Name var d D d.a.Name="bingle" }
6、嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
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 } type Monster struct { Name string Age int } type E struct { Monster int n int } func main() { //嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值 tv := TV{Goods{"电视机001", 5000.99}, Brand{"海尔", "山东"}} //演示访问Goods的Name fmt.Println(tv.Goods.Name) fmt.Println(tv.Price) tv2 := TV{ Goods{ Price: 5000.99, Name: "电视机002", }, Brand{ Name: "夏普", Address: "北京", }, } fmt.Println("tv", tv) fmt.Println("tv2", tv2) tv3 := TV2{&Goods{"电视机003", 7000.99}, &Brand{"创维", "河南"}} tv4 := TV2{ &Goods{ Name: "电视机004", Price: 9000.99, }, &Brand{ Name: "长虹", Address: "四川", }, } fmt.Println("tv3", *tv3.Goods, *tv3.Brand) fmt.Println("tv4", *tv4.Goods, *tv4.Brand) }
type Goods struct { Name string Price float64 } type Brand struct { Name string Address string } type TV struct { Goods Brand }
//嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值 tv := TV{ Goods{"电视机001", 5000.99}, Brand{"海尔", "山东"}, } //演示访问Goods的Name fmt.Println(tv.Goods.Name) fmt.Println(tv.Price)
2、为了保证代码的简洁性,建议大家尽量不使用多重继承
接口
USB插槽就是现实中的接口
你可以把手机、相机、U盘都插在USB插槽上,而不用担心那个插槽是专门插哪个的,原因是,做USB插槽的厂家和生产各种设备的厂家,都遵守了统一的规定,包括尺寸、排线等。
package main import "fmt" // 声明/定义一个接口 type Usb interface { // 声明了两个没有实现的方法 Start() Stop() } type Phone struct { } // 让 Phone 实现 Usb 接口的方法 func (phone Phone) Start() { fmt.Println("手机开始工作。。。") } func (phone Phone) Stop() { fmt.Println("手机停止工作。。。") } type Camera struct { } // 让 Camera 实现 Usb 接口的方法 func (camera Camera) Start() { fmt.Println("相机开始工作。。。") } func (camera Camera) Stop() { fmt.Println("相机停止工作。。。") } // 计算机 type Computer struct { } // 编写一个方法 Working 方法,接收一个 Usb 接口类型变量 // 只要是实现了 Usb 接口 (所谓实现 Usb 接口,就是指实现了 Usb 接口声明所有方法) // usb 变量会根据传入的实参,来判断到底是 Phone,还是 Camera func (computer 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) }
接口概念
小结:
1、接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低偶合的思想。
2、Golang 中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang 中没有 implement 或者C#中的 : 这样的关键字
package main import "fmt" type AInterface interface { Say() } type Student struct { Name string } func (student Student) Say() { fmt.Println("student Say()...") } func main() { var student Student // 结构体变量,实现了Say() 实现了 AInterface var a AInterface = student a.Say() }
2、接口中所有的方法都没有方法体,即都是没有实现的方法。
3、在 Golang 中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口。(在C#中,一个类继承接口,需要实现这个接口的所有方法,而且VS编译器会提醒需要实现接口中的方法)
4、一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
5、只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。
package main import "fmt" type AInterface interface { Say() } type integer int func (i integer) Say() { fmt.Println("integer Say i is ", i) } func main() { var i integer = 20 var b AInterface = i b.Say() }
6、一个自定义类型可以实现多个接口(多实现,单继承)
package main import "fmt" type AInterface interface { Say() } type BInterface interface { Hello() } type Person struct { } func (p Person) Hello() { fmt.Println("Person Hello()~~") } func (p Person) Say() { fmt.Println("Person Say()~~") } func main() { var person Person var a2 AInterface = person var b2 BInterface = person a2.Say() b2.Hello() }
7、Golang 接口中不能有任何变量
8、一个接口(比如 A 接口)可以继承多个别的接口(比如 B,C 接口),这时如果要实现 A 接口,也必须将 B,C 接口的方法也全部实现。
9、interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil
10、空接口 interface{} 没有任何方法,所以所有类型都实现了空接口, 即我们可以把任何一个变量赋给空接口。
package main import ( "fmt" "sort" ) func main() { // 先定义一个数组/切片 var intSlice = []int{0, -1, 10, 7, 90} // 要求对 intSlice切片进行排序 // 1.冒泡排序... // 2.也可以使用系统提供的方法 sort.Ints(intSlice) // 中文文档 http://docscn.studygolang.com/pkg/sort/#Ints fmt.Println(intSlice) }
这个地方,使用冒泡排序或者系统提供的排序方法,实现了排序。
实现对 Hero 结构体切片的排序: sort.Sort(data Interface)
系统源码中,使用了快速排序
我们需要实现 Interface 这个接口
代码如下:
package main import ( "fmt" "math/rand" "sort" ) // 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 //修改成对Name排序 //return hs[i].Name < hs[j].Name } func (hs HeroSlice) Swap(i, j int) { //交换 // temp := hs[i] // hs[i] = hs[j] // hs[j] = temp //下面的一句话等价于三句话 hs[i], hs[j] = hs[j], hs[i] } func main() { var heroes HeroSlice for i := 0; i < 10; i++ { hero := Hero{ Name: fmt.Sprintf("英雄|%d", rand.Intn(100)), Age: rand.Intn(100), } //将 hero append到 heroes切片 heroes = append(heroes, hero) } //看看排序前的顺序 for _, v := range heroes { fmt.Println(v) } //调用sort.Sort sort.Sort(heroes) fmt.Println("-----------排序后------------") //看看排序后的顺序 for _ , v := range heroes { fmt.Println(v) } }
(C#中更多的是将接口于抽象类的比较)
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, " 通过学习,会飞翔...") } //让LittleMonkey实现FishAble func (this *LittleMonkey) Swimming() { fmt.Println(this.Name, " 通过学习,会游泳..") } func main() { //创建一个LittleMonkey 实例 monkey := LittleMonkey{ Monkey { Name : "悟空", }, } monkey.climbing() monkey.Flying() monkey.Swimming() }
小结:
1、当 A 结构体继承了 B 结构体,那么 A 结构就自动的继承了 B 结构体的字段和方法,并且可以直接使用
2、当 A 结构体需要扩展功能,同时不希望去破坏继承关系,则可以去实现某个接口即可,因此我们可以认为:实现接口是对继承机制的补充
4、接口和继承解决的解决的问题不同
//编写一个方法Working 方法,接收一个Usb接口类型变量 //只要是实现了 Usb接口 (所谓实现Usb接口,就是指实现了 Usb接口声明所有方法) func (c Computer) Working(usb Usb) {// usb 变量会根据传进来的参数,来判断到底是Phone 还是Camear usb接口变量就体现出多态的特点 //通过usb接口变量来调用Start和Stop方法 usb.Start() usb.Stop() }
package main import ( "fmt" ) //声明/定义一个接口 type Usb interface { //声明了两个没有实现的方法 Start() Stop() } type Phone struct { name string } //让Phone 实现 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" func main() { var x interface{} var b2 float32 = 1.1 x = b2 // 空接口,可以接收任何类型 // x=> float32 【使用类型断言】 y := x.(float32) fmt.Printf("y 的类型是 %T 值是 =%v", y, y) }
对上面代码的说明
1、在进行类型断言时,如果类型不匹配,就会报 panic, 因此进行类型断言时,要确保原来的空接口指向的就是断言的类型.
2、如何在进行断言时,带上检测机制,如果成功就 ok,否则也不要报 panic
func main() { var x interface{} var b2 float32 = 1.1 x = b2 // 空接口,可以接收任何类型 // x=> float32 【使用类型断言】 //类型断言 带检测的 if y, ok := x.(float32); ok { fmt.Println("convert ok...") fmt.Printf("y 的类型是 %T 值是 =%v\n", y, y) } else { fmt.Println("convert fail...") } fmt.Println("go on ...") }
类型断言的最佳实践
package main import ( "fmt" ) type Usb interface { Start() Stop() } type Phone struct { name string } // 让 Phone 实现 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 (c 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 // Phone 还有一个特有的方法 call(),请遍历 Usb 数组,如果是 Phone 变量, // 除了调用 Usb 接口声明的方法外,还需要调用 Phone 特有方法 call. =》类型断言 var computer Computer for _, v := range usbArr { computer.Working(v) fmt.Println() } }