Go面向对象
一、Go的面向对象
Golang也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说Golang支持面向对象编程特性是比较准确的。
Golang没有类(class),Go语言的结构体(struct)和其它编程语言的类(class)有同等的地位,你可以理解Golang是基于struct来实现OOP特性的。
Golang面向对象编程非常简洁,去掉了传统OOP语言的继承、方法重载、构造函数和析构函数、隐藏的this指针等等
Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP语言不一样,比如继承: Golang没有extends 关键字,继承是通过匿名字段来实现。
Golang面向对象(OOP)很优雅,OOP本身就是语言类型系统(type system)的一部分,通过接口(interface)关联,耦合性低,也非常灵活。后面同学们会充分体会到这个特点。也就是说在Golang中面向接口编程是非常重要的特性。
二、struct
定义
1 | type Cat struct { |
在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值)
布尔类型是false ,数值是0,字符串是””。
数组类型的默认值和它的元素类型相关,比如score [3]int则为[0,0,0],指针,slice,和map的零值都是nil ,即还没有分配空间。
结构体中如果是引用类型,不要补个结构体之间赋值会改变内容
创建结构体变量
1 | //1 |
- 第3种和第4种坊式返回的是结构体指针。
- 结构体指针访问字段的标准方式应该是:(*结构体指针).字段名,比如
(*person).Name = "tom"
- 但go做了一个简化,也支持结构体指针.字段名,比如
person.Name = "tom"
。更加符合程序员使用的习惯,go编译器底层对person.Name做了转化(*person).Name
struct的每个字段上,可以写上一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列号和反序列化。
问题: json处理后的字段名也是首字母大写,这样如果我们是将json后的字符串返回给其它程序使用,比如jquery,php等,那么可能他们的不习惯这个命名方式,怎么办?
解决方案
- 将Monster的字段首字母小写,这个行不通,你会发现处理后,返回的是空字符串,因为
json.Marshal
相当于在其它包访问monster结构体,你首字母小写,就不能在其它包访问。 - 使用tag标签来解决
1 | package main |
三、方法
基本介绍
Golang中的方法是作用在指定的数据类型上的(即。和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct。
1 | type A struct { |
- 在通过一个变量去调用方法时,其调用机制和函数─样。不一样的地方时,变量调用方法时,该变量本身也会作为一个参数传递到方法(如果变量是值类型,则进行值拷贝,如果变量是引用类型,则进行地质拷贝)
1 | func (recevier type) methodName(参数列表)(返回值列表){ |
- 为了提高效率通常使用指针的传递
1 | type Circle struct { |
Golang中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct,比如int , float32等都可以有方法
1
2
3
4
5
6
7
8
9
10type Integer int
func(i Integer) print(){
fmt.Println(i)
}
func testInteger(){
var i Integer = 10
i.print()
}方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。
如果一个变量实现了
string()
这个方法,那么fmt.Println默认会调用这个变量的String()进行输出1
2
3
4
5
6
7
8
9
10
11
12
13
14
15type B struct {
b int
}
func (b *B) String() string{
str := fmt.Sprintf("b=[%v]",b.b)
return str
}
func testString(){
b := B{
b :1,
}
fmt.Println(&b)
}
方法和函数的区别
调用方式不一样函数的调用方式:
函数名(实参列表)
方法的调用方式:变量.方法名(实参列表)
对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以
1
2
3
4
5
6
7
8
9
10
11
12
13
14//这里才能觉得是值传递函数指针传递
func (b B) testPointer(){
b.b = 10
fmt.Println("method",b.b)
}
func test02(){
b := B{1}
(&b).testPointer()//这里只是将指针的值传递了过去
fmt.Println("main",b.b)
}
//method 10
//main 1不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和哪个类型绑定.
如果是和值类型,比如(p Person),则是值拷贝,如果和指针类型,比如是(p*Person)则是地址拷贝。
四、工厂模式
Golang的结构体没有构造函数,通常可以使用工厂模式来解决这个问题。
1 | package model |
因为这里的Student的首字母s是大写的,如果我们想在其它包创建Student 的实例(比如main包),引入model包后,就可以直接创建Student结构体的变量(实例)。但是问题来了,如果首字母是小写的,比如是type student struct …就不不行了,怎么办-工工厂模式来解决.
1 | package main |
五、继承
继承可以解决代码复用
当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体(比如刚才的Student),在该结构体中定义这些相同的属性和方法。
也就是说:在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。
1 | type Goods struct{ |
结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用。
当我们直接通过b 访问字段或方法时,其执行流程如下比如
b.Name
,编译器会先看b对应的类型有没有Name,如果有,则直接调用B类型的Name字段,如果没有就去看B中嵌入的匿名结构体A有没有声明Name字段,如果有就调用,如果没有继续查找..如果都找不到就报错.当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分.
如果一个struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字
1
2
3
4type Book struct{
good Goods
Writer string
}
六、接口
interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。到某个自定义类型(比如结构体Phone)要使用的时候,在根据具体情况把这些方法写出来。
1 | type 接口名 interface{ |
- 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低偶合的思想。
- Golang中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang中没有implement这样的关键字
1 | package main |
注意事项
- 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)
- 接口中所有的方法都没有方法体,即都是没有实现的方法。
- 在Golang中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口。
- 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型。
- 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。
- 一个自定义类型可以实现多个接口
- Golang接口中不能有任何变量
- 一个接口(比如A接口)可以继承多个别的接口(比如B,C接口),这时如果要实现A接口,也必须将B,c接口的方法也全部实现。
- interface类型默认是一个指针,I如果没有对interface初始化就使用,那么会输出nil
- 空接口interface没有任何方法,所以所有类型都实现了空接口
1 | type Test interface { |
接口和继承
- 接口和继承解决的解决的问题不同
继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。 - 接口比继承更加灵活
接口比继承更加灵活,继承是满足 is - a的关系,而接口只需满足 like - a的关系。 - 接口在一定程度上实现代码解耦
七、多态
变量(实例)具有多种形态。面向对象的第三大特征,在Go语言,多态特征是通过接口实现的。可以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。
- 多态参数
在前面的Usb接口案例,Usb usb,即可以接收手机变量,又可以接收相机变量,就体现了Usb接口多态 - 多态数组
给Usb数组中,存放Phone 结构体和Camera结构体变量
八、类型断言
1 | package main |
将一个接口变量,赋给自定义类型的变量.=>引出类型断言.
类型断言判断
1 | y, ok := x.(float64) |