2022-5-13 实习Day35

1、行内汇款——申报交易完成在监管报送平台查询不到问题排查。 –3小时 20%
2、汇入汇款不申报,有境外申报单问题排查 –2小时 80%
3、代理行业务测试 –2小时 30%

Golang编程学习(part 30)

1、面向对象编程思想—抽象的介绍

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

2、面向对象编程三大特性—封装

封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其他包只有通过被授权的操作(方法),才能对字段进行操作【封装的好处:1)隐藏实现细节。2)可以对数据进行验证,保证安全合理】

① 封装的实现步骤

1)将结构体、字段(属性)的首字母小写【不能导出了,其他包不能使用,类似private】

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

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

1
2
3
4
func (var 结构体类名) SetXxx(参数列表) (返回值列表) {
// 加入数据验证的业务逻辑
var.字段 = 参数
}

4)提供一个首字母大写的Get方法(类似其他语言的public),用于获取属性的值

1
2
3
func (var 结构体类名) GetXxx(){
return var.age
}
特别说明:在Golang中没有特别强调封装,这点并不像java,所以提醒学过java的同学,不用总是用java的语法特性来看待Golang,Golang本身对面向对象的特性做了简化的。
② 快速入门案例
1
2
// 请大家看一个程序(person.go),不能随便查看人的年龄、工资等隐私,并对输入的年龄进行合理的
// 验证。设计:model包(person.go) main包(main.go调用Person结构体)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package model
import ("fmt")
type person struct {
age int
salary float64
}

func NewPerson(age int, salary float64) (*person){
return &person{age, salary}
}
func (p *person) SetSalary(salary float64) {
(*p).salary = salary
}
func (p *person) GetSalary() (float64){
return (*p).salary
}
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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"Test1/model"
"fmt"
)

func main() {
person := model.NewPerson(25, 300000)
fmt.Println(*person)
person.SetAge(30)
person.SetSalary(100)

fmt.Println(person.GetAge(), person.GetSalary())
fmt.Println(*person)
}

{25 300000}
30 100
{30 100}

3、面向对象编程三大特性—继承

为什么需要继承?比如小学生和大学生,他们都有一些相同的字段和方法。所以这也就造成了我们会写一些相同的代码,代码的重复利用性不强。其次,代码也比较冗余,而且不利于维护,同时也不利于功能的扩展。因此我们要通过继承的方式来解决该问题

继承可以解决代码复用,让我们的编程更加靠近人类思维。当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体(比如刚才的Student),在该结构体中定义这些相同的属性和方法。

其他的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个Student匿名结构体即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
                            |------------------|
| Student |
| 共有的字段 |
| 共有的方法 |
|------------------|
/|\ /|\
| |
| |
| |
|----------------| |------------------|
| Pupil | | Graduate |
| 特有字段 | | 特有字段 |
| 特有方法 | | 特有方法 |
|匿名结构体Student| | 匿名结构体Student|
| (实现继承特性) | | (实现继承特性) |
|----------------| |-----------------|
也就是说:在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名
结构体的字段和方法,从而实现了继承特性
① 嵌套匿名结构体的基本语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import "fmt"

type Goods struct {
Name string
Price int
}

type Book struct {
Goods // 这里就是嵌套匿名结构体Goods
Writer string
}

func main() {
b := new(Book)
(*b).Writer = "尼克"
(*b).Price = 26
(*b).Name = "活着"
//(*b).Goods = Goods{"活着", 26}
//(*b).Goods.Price = 26
//(*b).Goods.Name = "活着"
fmt.Println(*b)
}

{{活着 26} 尼克}

② 继承的深入讨论

1)结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或小写的字段、方法,都可以使用。【跨包会有问题】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main
import (
"fmt"
)

type Student struct {
Name string
age int
}

func (s *Student) SayOk() {
fmt.Println("A SayOk", (*s).Name)
}

func (s *Student) hello() {
fmt.Println("hello", (*s).Name)
}

type Graduate struct {
Student
}

func main() {
g := new(Graduate)
(*g).Student.Name = "tom"
(*g).Student.age = 25
(*g).Student.SayOk()
(*g).Student.hello()
}

A SayOk tom
hello tom

2)匿名结构体字段访问可以简化

1
2
3
4
(*g).Name = "tom"
(*g).age = 25
(*g).SayOk()
(*g).hello()

对上面代码小结:

【1】当我们直接通过g访问字段或者方法时,其执行流程如下,比如(*g).Name
【2】编译器会先看g对应的类型有没有Name,如果有,则直接调用Gradute类型的Name字段
【3】如果没有就去看B中嵌入的匿名结构体Student有没有声明Name字段,如果有就调用,如果没有就继续查找,如果都找不到就报错

3)当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main
import "fmt"
type Student struct {
Name string
age int
}
type Graduate struct {
Student
Name string
}

func (s *Student) hello() {
fmt.Println("hello", (*s).Name)
}
func (s *Graduate) hello() {
fmt.Println("hello", (*s).Name)
}
func main() {
var g Graduate
// 这时就是就近原则,会访问Graduate结构体的Name字段
g.Name = "gra"
fmt.Println(g) // {{ 0} gra}

// 这种方式就明确指定访问Student匿名结构体的Name字段
g.Student.Name = "jackstudent"
fmt.Println(g) //{{jackstudent 0} gra}

// 就近原则,会访问Graduate结构体的hello方法
g.hello() // hello gra
// 明确指定访问Student匿名结构体的hello方法
g.Student.hello() // hello jackstudent
}

4)结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时就必须明确指定匿名结构体名字,否则编译报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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.Name="tom" Ambiguous reference 'Name'
c.A.Name = "jack"
c.B.Name = "lisa"
fmt.Println(c) // {{jack 0} {lisa 0}}
}

5)如果一个struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字

1
2
3
4
5
6
7
type D struct {
a A // 有名结构体
}

// 如果D中是一个有名结构体,则访问有名结构体的字段时,就务必带上有名结构体的名字
var d D
d.a.Name = "jack"

6)嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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{"电脑", 5000},
Brand{"夏普", "中国"},
}
fmt.Println(tv)
tv2 := TV2{
&Goods{
"电视",
6000,
},
&Brand{
"小米",
"中国",
},
}
fmt.Println(*tv2.Brand, *tv2.Goods)
}

{{电脑 5000} {夏普 中国}}
{小米 中国} {电视 6000}

7)结构体的匿名字段是基本数据类型,如何访问?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
import "fmt"
type Monster struct {
Name string
Age int
}
type Boss struct {
Monster
int
// int Duplicate field 'int'
n int
}

func main() {
// 演示一下 匿名字段时基本数据类型的使用
var b Boss
b.Name = "树妖"
b.Age = 300
b.int = 2
b.n = 50
fmt.Println(b)
}


{{树妖 300} 2 50}

如果一个结构体有int类型的匿名字段,就不能有第二个
如果需要多个int的字段,则必须给int字段指定名字