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包进行编写如下代码

go 复制代码
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 嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。

go 复制代码
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. 继承的字段和方法

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

go 复制代码
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 嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。

go 复制代码
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. 基本语法

go 复制代码
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{}没有任何方法,所以所有类型都实现了空接口,即我们可以把任何一个变量赋给空接口。
go 复制代码
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
}

四、多态

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

那代码世界呢?我如何表达狗,也可以叫做动物呢?中间的关系纽带是什么呢?

答案就是上边刚学到的--接口!

go 复制代码
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. 多态参数

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

go 复制代码
// 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. 多态数组

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

go 复制代码
var animal Animal = &Cat{}

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

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

	fmt.Println(animalArr)

3. 类型断言

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

go 复制代码
	// 结构体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

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

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

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

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

相关推荐
追风少年ii2 小时前
单细胞空间联合分析新贵--iStar
python·数据分析·空间·单细胞
antonytyler3 小时前
机器学习实践项目(二)- 房价预测增强篇 - 特征工程四
人工智能·python·机器学习
gCode Teacher 格码致知3 小时前
Python教学基础:用Python和openpyxl结合Word模板域写入数据-由Deepseek产生
python·word
饼干,3 小时前
第5天python内容
开发语言·python
召唤神龙3 小时前
爬虫代理IP池搭建指南:实测推荐高可用服务商
爬虫·tcp/ip
ZhengEnCi4 小时前
P3E-Python Lambda表达式完全指南-什么是匿名函数?为什么90%程序员都在用?怎么快速掌握函数式编程利器?
后端·python
Ace_31750887764 小时前
京东商品详情接口深度解析:从反爬绕过到数据结构化重构
数据结构·python·重构
尤利乌斯.X4 小时前
在Java中调用MATLAB函数的完整流程:从打包-jar-到服务器部署
java·服务器·python·matlab·ci/cd·jar·个人开发
听风吟丶4 小时前
Java 9 + 模块化系统实战:从 Jar 地狱到模块解耦的架构升级
开发语言·python·pycharm