zoukankan      html  css  js  c++  java
  • 十一、面向对象编程_下

    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
    ",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
    ", index, x)
    			case float32 :
    				fmt.Printf("第%v个参数是float32 类型,值是%v
    ", index, x)
    			case float64 :
    				fmt.Printf("第%v个参数是float64 类型,值是%v
    ", index, x)
    			case int,int32,int64 :
    				fmt.Printf("第%v个参数是整数类型,值是%v
    ", index, x)
    			case string :
    				fmt.Printf("第%v个参数是string类型,值是%v
    ", index, x)
    			default :
    				fmt.Printf("第%v个参数 是类型不确定,值是%v
    ", 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)
    }
    
  • 相关阅读:
    南京网络赛题解 2018
    ACM 第二十天
    存储型XSS漏洞(pikachu)
    XSS跨站脚本(pikachu)——反射型xss(get和post)
    暴力破解(pikachu)——防范措施(简单了解)
    暴力破解02(pikachu)——验证码
    暴力破解01(pikachu)——基于表单
    安全软件burp suite pro破解版(亲测有效)
    sql lab的基础配置和1-4关闯关攻略(建议自学MySQL语言后再观看)
    sql lab的环境配置以及火狐浏览器的使用
  • 原文地址:https://www.cnblogs.com/jiaxiaozia/p/13069264.html
Copyright © 2011-2022 走看看