16、做中学 | 初三上期 Golang面向对象_进阶

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

接下来进行探索golang中的三大特性:封装、继承、多态!

一、封装

1. 什么是封装?

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

在这里插入图片描述
平常所看的电视,各种电路,电线进行封装隐藏,避免直接操作线路造成破坏和人身安全!

在代码中,我们把想封装的内容,进行封装隐藏,只暴露简单安全的访问通道!

2. 封装的好处

  1. 隐藏实现细节
  2. 提可以对数据进行验证,保证安全合理

3. 如何体现封装

  1. 对结构体中的属性进行封装
  2. 通过方法,包 实现封装

4. 封装的实现步骤

  1. 将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似 private
  2. 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
  3. 提供一个首字母大写的 Set 方法(类似其它语言的 public),用于对属性判断并赋值
  4. 提供一个首字母大写的 Get 方法(类似其它语言的 public),用于获取属性的值

在 Golang 开发中并没有特别强调封装,这点并不像 Java, Golang 本身对面向对象的特性做了简化的.

创建grade090包进行编写如下代码

package grade090

type People struct {
	//  姓名 首字母大写 表示该字段是公共字段
	Name string

	// 年龄 首字母小写 表示该字段是私有字段
	age int
}

// NewPeople 构造函数通过工厂方法进行创建
func NewPeople(name string) *People {
	return &People{
		Name: name,
	}
}

// GetAge 获取age
func (people *People) GetAge() int {
	return people.age
}

// SetAge 修改age
func (people *People) SetAge(age int) {
	people.age = age
}

通过工厂方法进行创建实例,age字段为私有,通过Setxxx、Getxxx方法首字母大写进行开放调用

Name为啥可以直接访问,因为首字母大写,是公共字段,可以在包外直接访问
在这里插入图片描述

不要觉得可以直接修改age,为啥还有加俩个方法,才进行修改age!封装的目的就是安全!

二、继承

子承父业,在现实社会中,我们通常进行各种学习,以达到自己冷暖自知的地步!

在代码中,上一个结构体抽象出了基本的属性和行为,但是相近的结构体也有大部分相同的属性和行为怎么办呢?需要在写一份相同的内容吗? 代码层面上,要做到简洁、高效!golang中解决重复代码的方法,就是继承!!!

1. 实现继承

在 Golang 中,如果一个 struct 嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。

package main

import "fmt"

type Goods struct {
	// 商品名称
	name string

	// 商品价格
	price float64
}

type Book struct {
	// 继承Goods
	Goods

	// 作者
	author string
}

func main() {
	var book Book = Book{}
	book.name = "Go语言"
	book.price = 99.9
	book.author = "雨中散步撒哈拉"
	fmt.Println(book.name, book.price, book.author)
	// Go语言 99.9 雨中散步撒哈拉
}

商品的共性是有名称和价格,其中一种商品为书籍,也有其名称和售价,这时使用继承,进行匿名嵌套商品结构体,书籍具有了商品名称和价格的属性。

2. 继承的好处

  1. 代码的复用性提高了
  2. 代码的扩展性和维护性提高了

3. 继承的字段和方法

在我们进行继承的过程中,怎么知道我调用的是自己的属性/方法,还是上层的呢?

package main

import "fmt"

type Ball struct {
	// 球的材质
	material string

	// 价格
	price float64
}

// GetBallInfo 获取球的信息
func (this *Ball) GetBallInfo() {
	fmt.Println("球的材质:", this.material, "价格:", this.price)
}

// GetBallAddInfo 获取球地址生产信息
func (this *Ball) GetBallAddInfo() {
	fmt.Println("球的材质:", this.material, "价格:", this.price)
}

type Basketball struct {
	// 继承Ball
	Ball

	// 球的尺寸
	size string
}

// GetBallInfo 获取篮球信息
func (this *Basketball) GetBallInfo() {
	fmt.Println("篮球的材质:", this.material, "价格:", this.price)
}

func main() {
	var ball Ball = Ball{}
	ball.material = "橡胶"
	ball.price = 99.9

	var basketball Basketball = Basketball{}
	basketball.material = "化学材质"
	basketball.price = 9.9
	basketball.size = "35"

	fmt.Println(basketball.material)
	// 化学材质
	fmt.Println(basketball.size)
	// 35
	
	basketball.GetBallInfo()
	// 篮球的材质: 化学材质 价格: 9.9

	basketball.GetBallAddInfo()
	// 球的材质: 化学材质 价格: 9.9

}

球和篮球,俩个结构体都具有材质和价格属性、基本信息方法,篮球有尺寸属性,球有生产地址方法。

在调用过程中发现

  1. 属性:先看调用着自身是否有该字段,如果有,则用调用者自身属性,如果没有,则用继承的属性
  2. 方法:基本和属性一样,先找自己是否有,有则用自己的,无则用继承的方法

就近原则!

4. 多重继承

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

package main

import "fmt"

type Brand struct {
	// 名称
	Name string

	// 价格
	Price float64
}

type Factory struct {
	// 工厂名称
	Name string

	// 地址
	Address string
}

type FootBall struct {
	Brand

	Factory
}

func main() {
	var footBall FootBall = FootBall{}
	// 设置品牌属性
	footBall.Brand.Name = "Nike"
	footBall.Price = 99.9

	// 设置工厂属性
	footBall.Factory.Name = "梦想工厂"
	footBall.Address = "北京"

	fmt.Println(footBall.Brand.Name, footBall.Price, footBall.Factory.Name, footBall.Address)
	// Nike 99.9 梦想工厂 北京
}

嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分。

三、接口

多态的实现,需要借用接口进行扩展!这里先介绍下接口!

1. 接口说明

interface 类型可以定义一组方法,但是这些不需要实现。并且 interface 不能包含任何变量。到某个自定义类型要使用的时候,在根据具体情况把这些方法写出来(实现)。

2. 基本语法

type 接口名称 interface {
	mothod1(参数列表) 返回值列表
	mothod2(参数列表) 返回值列表
}
  1. 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低偶合的思想。
  2. Golang 中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang 中没有implement这样的关键字
  3. 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)
  4. 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
  5. 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。
  6. 一个自定义类型可以实现多个接口
  7. Golang 接口中不能有任何变量
  8. 一个接口(比如 A 接口)可以继承多个别的接口(比如 B.C接口),这时如果要实现 A接口,也必须将 B.C接口的方法也全部实现。
  9. interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil
  10. 空接口 interface{}没有任何方法,所以所有类型都实现了空接口,即我们可以把任何一个变量赋给空接口。
package main

import "fmt"

type EnglishSkills interface {
	// SayEnglish 说英语
	SayEnglish()
}

type Student struct {
	// 姓名
	name string
}

func (this *Student) SayEnglish() {
	fmt.Println("Student:", this.name, "SayEnglish")
}

func main() {
	var student Student = Student{}
	student.name = "张三"
	student.SayEnglish()
	// Student: 张三 SayEnglish
}

四、多态

现实世界中,看到一条狗,我们既可以叫它狗,也可以叫它动物!这种现象是生物界繁衍产生的!

那代码世界呢?我如何表达狗,也可以叫做动物呢?中间的关系纽带是什么呢?
答案就是上边刚学到的–接口!

package main

import "fmt"

// Animal 动物
type Animal interface {
	// Smell 嗅觉灵敏
	Smell()
}

// Dog 狗
type Dog struct {
	Name string
}

// Smell 嗅觉灵敏  Dog进行实现
func (a *Dog) Smell() {
	fmt.Println("狗的嗅觉很灵敏...")
}

// Cat 猫
type Cat struct {
	Name string
}

// Smell 嗅觉灵敏  Dog进行实现
func (a *Cat) Smell() {
	fmt.Println("猫的嗅觉很灵敏...")
}


Dog/Cat结构体如果全部实现了接口Animal方法,那么Dog/Cat就实现Animal接口!

1. 多态参数

在传递参数的过程中,形参可以使用接口的类型进行传递参数。

// animalSmell 多态参数
func (this *Cat) animalSmell(animal Animal) {
	animal.Smell()
}

func main() {
	cat := Cat{}
	dog := Dog{}

	// 传入不同的实现类型
	cat.animalSmell(&cat)
	// 输出:猫的嗅觉很灵敏...

	cat.animalSmell(&dog)
	// 输出:狗的嗅觉很灵敏...
}

animalSmell方法形参为Animal,实参为实现了Animal接口的结构体实例,输出结果是传入实例的方法输出!

接口是模板,结构体是样品,多态就是按模板造出不同却兼容的样品。

2. 多态数组

结构体在创建对象的过程中,后边是各种结构体,前边则是实现的接口类型,这种现象为多态!是编程语言扩展功能的方式!

var animal Animal = &Cat{}

如果把上边多个创建对象合并到一个数组中,则为多态数组,其实看懂上边这一行就行!!!

	var animalArr [3]Animal
	animalArr[0] = &Cat{"小白"}
	animalArr[1] = &Dog{"二哈"}
	animalArr[2] = &Cat{"小黑"}

	fmt.Println(animalArr)

3. 类型断言

由多态延申过来,多态过程中,判断接口接收到的当前对象,是否为想要的类型!如果是则条件成立,如果否则条件失败!

	// 结构体Dog 实例化后,使用Animal接口接收
	var dog2 Animal = &Dog{}
	// 判断是否为Dog类型
	d := dog2.(*Dog)
	fmt.Printf("%T, %v\n", d, d)
	// *main.Dog, &{}

	// 声明空接口x
	var x interface{}
	// 赋值f
	var f float32 = 3.14
	// 变量f  赋值给空接口x
	x = f
	// 判断x是否为float64
	var ff = x.(float64)
	// 抛出异常
	// panic: interface conversion: interface {} is float32, not float64

多态过程中:判断接口是否为某一个类型,而进行的判断

本篇进行介绍了面向对象的三大特性:封装、继承、多态

其中多态,需要使用接口的概念来进行实现,进而达到扩展的作用!

三大特性在开发过程中,经常用到,需要熟练使用,达到举一反三的目的!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值