十一、面向对象编程_下

11.1 面向对象编程思想-抽象

如何理解抽象

​ 我们在之前定义一个结构体时候,实际上就是把一类事物的共有的属性(字段)和行为()方法提出来,形成一个物理模型(结构体),这种研究问题的方法成为抽象。

987654345678.PNG

代码实现

package main
import (
	"fmt"
)
// 定义一个结构体Account
type Account struct {
	AccountNo string
	Pwd string
	Blance float64
}
// 方法:
// 1、存款
func (account *Account) Deposite(money float64, pwd string) {
	//看看输入的密码正确与否
	 if pwd != account.Pwd {
		 fmt.Println("你输入的密码不正确")
		 return
	 }
	//  看看存款金额是否正确
	if money <= 0 {
		fmt.Println("你输入的金额不正确")
		return
	}
	account.Blance += money
	fmt.Println("存款成功")
}
// 2、取款
func (account *Account) WithDraw(money float64, pwd string) {
	//看看输入的密码正确与否
	 if pwd != account.Pwd {
		 fmt.Println("你输入的密码不正确")
		 return
	 }
	//  看看取款金额是否正确
	if money <= 0 || money > account.Blance {
		fmt.Println("你输入的金额不正确")
		return
	}
	account.Blance -= money
	fmt.Println("取款成功")
}
// 3、查询
func (account *Account) Query(pwd string) {
	//看看输入的密码正确与否
	 if pwd != account.Pwd {
		 fmt.Println("你输入的密码不正确")
		 return
		 }
		 fmt.Printf("你的账号为=%v  余额为=%v\n",account.AccountNo, account.Blance)
	}

func main() {
	// 测试
	account := Account{
		AccountNo : "gs1111",
		Pwd : "66",
		Blance : 100.0,
	}
	for { 
		// 这里可以做的更加灵活,就是让用户通过控制台输入命令
		// 菜单......
		account.Query("66")
		account.Deposite(200.0, "66")
		account.Query("66")
		account.WithDraw(150.0, "66")
		account.Query("66")
	}
}

11.2 面向对象编程三大特性

11.2.1 面向对象编程-封装

11.2.1.1 介绍

​ 封装就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其他包只有通过被授权的操作(方法),才能对字段进行操作。

tht7vQ.png

11.2.1.2 封装的理解和好处
  • 隐藏实现细节
  • 可以对数据进行验证,保证安全合理
11.2.1.3 如何实现封装
  • 对结构体中的属性进行封装
  • 通过方法,包实现封装
11.2.1.4 封装实现步骤
  • 将结构体、字段(属性)的首字母小写(不能导出了,其他包不能使用,类似private)

  • 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数

  • 提供一个首字母大写的Set方法(类似其他语言public),用于对属性判断并赋值

    func (var 结构体类型名) SetXxx(参数列表)(返回值列表) {
     	// 加入数据验证的  业务逻辑
        var.字段 = 参数
    }
    
  • 提供一个首字母大写的Get方法(类似其他语言的public),用于获取属性的值

    func (var 结构体类型名) GetXxx() {
     	return var字段;
    }
    

    特别说明:在Golang开发中并没用特别强调封装,这点不想java,Golang本身对面向对象的特性做了简化的。

11.2.1.5 入门案例

​ 看一个程序person.go,不能查看人的年龄,工资的等隐私,并对输入的年龄进行合理的验证

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
}

main.go

package main
import (
	"fmt"
	"go_code/factory/model"
)

func main() {
	p := model.NewPerson("smith")
	p.SetAge(18)
	p.SetSal(3000)
	fmt.Println(p)
	fmt.Println(p.Name, "age=", p.GetAge(), "sal=", p.GetSal())
}

运行main.go文件

987654345678.PNG

11.2.2 面向对象编程-继承

11.2.2.1 继承基本介绍和示意图

​ 继承可以解决代码复用的问题,让我们的编程更加靠近人类思维

​ 当等多个结构体i在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法。

tIzafU.png

​ 其他的结构体不需要重新定义这些属性和方法, 只需要嵌套一个匿名结构体即可,也就是说,Golang中,如果一个Struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承性。

11.2.2.2 嵌套匿名结构体的基本语法
type Goods struct {
    Name string
    Price int
}

type Book struct {
    Goods   //这里就是嵌套匿名结构体Goods
    Writer string
}
11.2.2.3 继承讨论
  • 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段,方法,都可以使用
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()
}
  • 匿名结构体字段访问可以简化

你哈.PNG

对上面代码小结

​ 1、当我们直接通过b访问字段或者方法时,其执行流程如下比如:b.Name

​ 2、编译器会先看吧对应的类型有没有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)
}

type B struct {
	A 
	Name string
}

func (b *B) SayOK() {
	fmt.Println("B SayOk", b.Name)
}

func  main() {
	var b B 
	b.Name = "jack"  //OK
    //假如要是想给A.Name赋值,可以使用下面的这个 ,如果这里没有指明这个值,则下面的b.hello()则会输出一个空的字符串,不会输出ssss,因为没有值存在。采用就近原则
    b.A.Name = "ssss"
	b.age = 100    //ok
	b.SayOK()     //B SayOk jack
	b.hello()      // A hello 
}
  • 结构体嵌入两个(或多个匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错
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"
	// 这个规则对于方法也是一样的
	c.A.Name= "tom"	
	fmt.Println("c", c)
}
  • 如果一个struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字。

你哈.PNG

  • 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
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() {
	tv1 := TV{ Goods{"电视机001", 6000.99}, Brand{"夏普", "北京"}}
	tv2 := TV{ 
		Goods{
			Name : "电视机002", 
			Price : 5000.99,
		}, 
		Brand{
			 Name : "海尔", 
			 Address : "山东青岛",
		},
	}

	tv3 := &TV2{ &Goods{"电视机003", 6000.99}, &Brand{"创维", "河南"}}
	tv4 := TV2{ 
		&Goods{
			Name : "电视机004", 
			Price : 999.99,
		}, 
		&Brand{
			 Name : "小米", 
			 Address : "武汉",
		},
	}
	fmt.Println("tv1", tv1)
	fmt.Println("tv2", tv2)
	fmt.Println("tv3", tv3)
	fmt.Println("tv3", *tv3.Goods, *tv3.Brand)
	fmt.Println("tv4", tv4)
	fmt.Println("tv4", *tv4.Goods, *tv4.Brand)
}
11.2.2.4 多重继承

如一个struct嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。

11.3 接口(interface)

在Golang中动态特性主要是通过接口来体现的。

11.3.1 基本介绍

你哈.PNG

11.3.2 应用场景

你哈.PNG

11.3.3 注意事项和细节

你哈.PNG

你哈.PNG

你哈.PNG

①代码

你哈.PNG

⑤代码

你哈.PNG

⑥代码

你哈.PNG

你哈.PNG

⑧代码

你哈.PNG

你哈.PNG

⑩代码

你哈.PNG

11.3.4 接口实践

实现对Hero结构体 切片的排序:sort.Sort(data Interface)

package main
import (
	"fmt"
	"sort"
	"math/rand"
)
// 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[i].Age
}

func (hs HeroSlice) Swap(i, j int) {
	temp := hs[i]
	hs[i] = hs[j]
	hs[j] = temp
}
func main() {
	// 先定义一个数组/切片
	var intSlice = []int{0, -1, 10, 7, 90}
	// 要求对intSlice切片进行排序
	// 1、冒泡排序...
	// 2、也可以使用系统提供的方法
	sort.Ints(intSlice)
	fmt.Println(intSlice)

	// 对一个结构体切片进行排序
	// 1、冒泡排序
	// 2、也可以使用系统提供的方法
	// 测试,是否可以对结构体切片进行排序
	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)
	}
}

11.3.5 接口 VS 继承

toOfFU.png

代码

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, "生来会爬树")
}

// LittleMonkty结构体
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() {
	// 创建一个LittleMonkry 实例
	monkey := LittleMonkey {
		Monkey {
			Name : "悟空",
		},
	}
	monkey.climbing()
	monkey.Flying()
	monkey.Swimming()
}

对上面代码的小结

1、当A结构体继承了B结构体,那么A结构体就自动的继承了B结构体的字段和方法,并且可以直接使用

2、当A结构体需要扩展功能,同时不希望去破坏继承关系,则可以去实现某个接口即可,因此我们可以认为,实现接口是对继承机制的补充。

实现接口可以看作是对继承的一种补充

tLmnm9.png

11.3.6 接口和继承解决的问题不同

​ 继承的价值主要在于:解决代码的复用性和可维护性

​ 接口的价值主要在于:设计、设计好各种规范(方法),让其他自定义类去实现这些方法

接口比继承更加灵活

​ 接口比继承更加灵活,继承是满足 is - a 的关系。而接口只需要满足 like - a 的关系

接口在一定程度啥很难过实现代码解耦

11.3 面向对象编程—多态

11.3.1 基本介绍

变量(实例)具有多种形态,面向对象的第三大特征,在Go语言,多态特征是通过接口实现的,可以按照统一的接口来调用不同的实现,这时接口变量就呈现不同的形态。

11.3.2 接口体现多态特征

1、多态参数

2、多态数组

11.4 类型断言

tLuKG6.png

11.4.1 基本介绍

类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言。具体如下:

package main
import (
	"fmt"
)
func main(){
	var x interface{}
	var b float32 = 1.1
	 x = b //空接口,可以接收任意类型
	 //x=>float32 [使用类型断言]
	 y := x.(float32)
	 fmt.Printf("y 的类型是%T 值是=%v", y, y)
}

对上面代码说明

在进行类型断言时,如果类型不匹配,就会报panic,因此进行类型断言时,要确保 原来的空接口指向的就是要断言的类型。

tLQJZ4.png

升级

如何在进行断言时,带上检测机制。如果成功就OK,否则也不要报一个panic

tLlZ6K.png

11.4.2 案例

写一函数,循环判断传入参数的类型

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.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 = "tom"
	address := "北京"
	n4 := 300
	TypeJudge(n1, n2, n3, name, address , n4)
}

posted on 2020-06-08 23:07  九酒馆  阅读(187)  评论(0编辑  收藏  举报

导航