Featured image of post golang学习十四:golang中的面向对象

golang学习十四:golang中的面向对象

Go语言中的面向对象

  • 面向对象是一种思想,到目前为止还没有一个非常明确的定义,老程序员在不同时期对面向对象的理解是不同的.Go语言中对面向对象有着自己的理解

Although there is no universally accepted definition of object-oriented programming, for our purposes, an object is simply a value or variable that has methods , and a method is a function assiociated with a particular type.

  • 通过上面解释看出了Go语言开发者认为:面向对象就是特定类型(结构体)有这自己的方法,利用这个方法完成面向对象编程,并没有提封装、继承、多态.所有Go语言进行面向对象编程时,重点在于灵活使用方法.Go语言通过这样的设计降低了语言学习的压力.
  • Go语言有着自己对面向对象的理解,他也有着自己的封装、继承、多态

封装

  • 封装主要体现在两个方面:封装数据、封装业务
  • Go语言中通过首字母大小控制访问权限.属性首字母小写对外提供访问方法是封装数据最常见的实现方式
  • 可以通过方法封装业务
    • 提出方法是封装
    • 控制结构体属性访问,对外提供访问方法也是封装
  • 在面向对象中封装的好处:
    • 安全性.结构体属性访问受到限制,必须按照特定访问渠道
    • 可复用性,封装的方法实现可复用性
    • 可读写,多段增加代码可读性

代码实现

  • Go语言同包任意位置可以访问全局内容,封装控制可以控制包外访问结构体中数据
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
type People struct {
	name string //姓名
	age  int    //体重.单位斤
}

func (p *People) SetName(name string) {
	p.name = name
}
func (p *People) GetName() string {
	return p.name
}

func (p *People) SetAge(age int) {
	p.age = age
}

func (p *People) GetAge() int {
	return p.age
}
  • 封装业务就是根据自己的需求提取代码,使用Go语言标准库中的函数过程就属性封装业务(代码)

继承

  • 按照传统面向对象思想,继承就是把同一类事物提出共同点为父类,让子类可以复用父类的可访问性内容.
  • 继承有多种实现方式
    • 通过关键字继承,强耦合实现方式
    • 组合式继承,松耦合继承方式
  • 使用过Java或C#的应该知道尽量少用继承而是使用组合代替继承,可以使用高内聚,低耦合.Java之父之前在一次采访的时候也说过,如果给他一次机会重新做Java,他最希望修改的地方就是继承
  • Go语言中的继承是通过组合实现

匿名属性

  • 在Go语言中支持匿名属性(结构体中属性名字),但是每个最多只能存在匿名属性.编译器认为类型就是属性名,我们在使用时就把类型当作属性名进行使用
1
2
3
4
5
6
7
8
9
type People struct {
	string
	int
}

func main() {
	p:=People{"msr",22}
	fmt.Println(p.string,p.int)
}

结构体之间的关系

  • 传统面向对象中类与类之间的关系
    • 继承:is-a,强耦合性,一般认为类与类之间具有强关系
    • 实现:like-a,接口和实现类之间的关系
    • 依赖:use-a,具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到A,一般作为方法参数
    • 关联:has-a一种强依赖关系,比如我和我的朋友;这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的、关联可以是单向、双向的
    • 聚合:has-a,整体与部分、拥有的关系
    • 组合:contains-a,他体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合;他同样体现整体与部分间的关系,但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束
    • 组合>聚合>关联>依赖
  • Go语言中标准的组合关系
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
type People struct {
	name string
	age  int
}

type Teacher struct {
	peo       People
	classroom string //班级
}

func main() {
	teacher := Teacher{People{"msr", 17}, "302教室"}
	//必须通过包含的变量名调用另一个结构体中内容
	fmt.Println(teacher.classroom, teacher.peo.age, teacher.peo.name)
}

使用匿名属性完成Go语言中的继承

  • Go语言中的继承很好实现,把另一个结构体类型当作另一个结构体的属性,可以直接调用另一个结构体中的内容
  • 因为Go语言中结构体不能相互转换,所以不能把子结构体变量赋值给父结构体变量
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
type People struct {
	name string
	age  int
}

type Teacher struct {
	People
	classroom string //班级
}

func main() {
	teacher := Teacher{People{"msr", 22}, "302教室"}
	fmt.Println(teacher.classroom, teacher.age, teacher.name)
}

接口

  • 接口解释:接口是一组行为规范的定义.
  • 接口中只能有方法声明,方法只能有名称、参数、返回值,不能有方法体
  • 每个接口中可以有多个方法声明,结构体把接口中 所有 方法都重写后,结构体就属于接口类型
  • Go语言中接口和结构体之间的关系是传统面向对象中is-like-a的关系
  • 定义接口类型关键字是interface
1
2
3
type 接口名 interface{
  方法名(参数列表) 返回值列表
}
  • 接口可以继承接口,且Go语言推荐把接口中方法拆分成多个接口

代码示例

  • 接口中声明完方法,结构体重写接口中方法后,编译器认为结构体实现了接口
    • 重写的方法要求必须和接口中方法名称、方法参数(参数名称可以不同)、返回值列表完全相同
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
type People struct {
	name string
	age  int
}

type Live interface {
	run(run int)
}

func (p *People) run(run int) {
	fmt.Println(p.name, "正在跑步,跑了,", run, "米")
}

func main() {
	peo := People{"张三", 17}
	peo.run(100)
}
  • 如果接口中有多个方法声明,接口体必须重写接口中全部方法才任务结构体实现了接口
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
type People struct {
	name string
	age  int
}

type Live interface {
	run(run int)
	eat()
}

func (p *People) run(run int) {
	fmt.Println(p.name, "正在跑步,跑了,", run, "米")
}
func (p *People) eat() {
	fmt.Println(p.name, "正在吃饭")
}

func main() {
	peo := People{"张三", 17}
	peo.run(100)
}
  • 接口可以继承接口(组合),上面代码可以改写成下面代码
 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
type People struct {
	name string
	age  int
}

type Live interface {
	run(run int)
	Eat
}

type Eat interface {
	eat()
}

func (p *People) run(run int) {
	fmt.Println(p.name, "正在跑步,跑了,", run, "米")
}
func (p *People) eat() {
	fmt.Println(p.name, "正在吃饭")
}

func main() {
	peo := People{"张三", 17}
	peo.run(100)
}

多态

  • 多态:同一件事情由于条件不同产生的结果不同
  • 由于Go语言中结构体不能相互转换,所以没有结构体(父子结构体)的多态,只有基于接口的多态.这也符合Go语言对面向对象的诠释
  • 多态在代码层面最常见的一种方式是接口当作方法参数

代码示例

  • 结构体实现了接口的全部方法,就认为结构体属于接口类型,这是可以把结构体变量赋值给接口变量
  • 重写接口时接收者为Type*Type的区别
    • *Type可以调用*TypeType作为接收者的方法.所以只要接口中多个方法中至少出现一个使用*Type作为接收者进行重写的方法,就必须把结构体指针赋值给接口变量,否则编译报错
    • Type只能调用Type作为接收者的方法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
type Live interface {
	run()
	eat()
}
type People struct {
	name string
}

func (p *People) run() {
	fmt.Println(p.name, "正在跑步")
}
func (p People) eat() {
	fmt.Println(p.name, "在吃饭")
}

func main() {
	//重写接口时
	var run Live = &People{"张三"}
	run.run()
	run.eat()
}
  • 既然接口可以接收实现接口所有方法的结构体变量,接口也就可以作为方法(函数)参数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Live interface {
	run()
}
type People struct{}
type Animate struct{}

func (p *People) run() {
	fmt.Println("人在跑")
}
func (a *Animate) run() {
	fmt.Println("动物在跑")
}

func sport(live Live) {
	fmt.Println(live.run)
}

func main() {
	peo := &People{}
	peo.run() //输出:人在跑
	ani := &Animate{}
	ani.run() //输出:动物在跑
}

断言

  • 只要实现了接口的全部方法认为这个类型属于接口类型,如果编写一个接口,这个接口中没有任何方法,这时认为所有类型都实现了这个接口.所以Go语言中interface{}代表任意类型
  • 如果interface{}作为方法参数就可以接收任意类型,但是在程序中有时有需要知道这个参数到底是什么类型,这个时候就需要使用断言
  • 断言使用时使用interface{}变量点括号,括号中判断是否属于的类型
1
2
i interface{}
i.(Type)
  • 断言的两大作用:
    • 判断是否是指定类型
    • 把interface{}转换为特定类型

代码示例

  • 断言可以有一个返回值,如果判断结果是指定类型返回变量值,如果不是指定类型报错
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func demo(i interface{}){
	result:=i.(int)
	fmt.Println(result)
}

func main() {
	/*
	参数是456时,程序运行正常,输出:
		456
	参数是false时报错:
		panic: interface conversion: interface {} is bool, not int
	 */
	demo(456)
}
  • 断言也可以有两个返回值,这时无论是否是指定类型都不报错.
    • 第一个参数:
      • 如果正确:返回值变量值
      • 如果错误:返回判断类型的默认值
    • 第二个参数:
      • 返回值为bool类型,true表示正确,false表示错误
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func demo(i interface{}) {
	result, ok := i.(int)
	fmt.Println(result, ok)
}

func main() {
	/*
	参数是456时,程序运行正常,输出:
		456	true
	参数是字符串"abc"时程序运行正常,输出:
		0 false
	 */
	demo("abc")
}

golang学习一:从环境配置开始到HelloWorld入门 golang学习二:golang自带的工具 olang学习三:golang基础语法 golang学习四:流程控制 golang学习五:常用数学函数与数组 golang学习六:for循环 golang学习七:goto和label golang学习八:切片 golang学习九:sort包、map、双向链表、双向循环链表 golang学习十:函数 golang学习十一:包的访问权限、变量作用域、闭包 golang学习十二:值传递和引用传递 golang学习十三:结构体 golang学习十四:golang中的面向对象 golang学习十五:错误异常处理 golang学习十六:文件操作 golang学习十七:反射 golang学习十八:XML操作 golang学习十九:日志 golang学习二十:golang并发编程入门 golang学习二十一:select和GC

Licensed under CC BY-NC-SA 4.0