上篇定义一个结构体时候,实际上就是把一类事物的共有的属性(字段)和行为(方法)提取出来,形成一个物理模型(结构体)。这种研究问题的方法称为抽象。
接下来进行探索golang中的三大特性:封装、继承、多态!
一、封装
1. 什么是封装?
封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作。

平常所看的电视,各种电路,电线进行封装隐藏,避免直接操作线路造成破坏和人身安全!
在代码中,我们把想封装的内容,进行封装隐藏,只暴露简单安全的访问通道!
2. 封装的好处
- 隐藏实现细节
- 提可以对数据进行验证,保证安全合理
3. 如何体现封装
- 对结构体中的属性进行封装
- 通过方法,包 实现封装
4. 封装的实现步骤
- 将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似 private
- 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
- 提供一个首字母大写的 Set 方法(类似其它语言的 public),用于对属性判断并赋值
- 提供一个首字母大写的 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. 继承的好处
- 代码的复用性提高了
- 代码的扩展性和维护性提高了
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
}
球和篮球,俩个结构体都具有材质和价格属性、基本信息方法,篮球有尺寸属性,球有生产地址方法。
在调用过程中发现
- 属性:先看调用着自身是否有该字段,如果有,则用调用者自身属性,如果没有,则用继承的属性
- 方法:基本和属性一样,先找自己是否有,有则用自己的,无则用继承的方法
就近原则!
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(参数列表) 返回值列表
}
- 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低偶合的思想。
- Golang 中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang 中没有implement这样的关键字
- 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)
- 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
- 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。
- 一个自定义类型可以实现多个接口
- Golang 接口中不能有任何变量
- 一个接口(比如 A 接口)可以继承多个别的接口(比如 B.C接口),这时如果要实现 A接口,也必须将 B.C接口的方法也全部实现。
- interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil
- 空接口 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
多态过程中:判断接口是否为某一个类型,而进行的判断
附
本篇进行介绍了面向对象的三大特性:封装、继承、多态
其中多态,需要使用接口的概念来进行实现,进而达到扩展的作用!
三大特性在开发过程中,经常用到,需要熟练使用,达到举一反三的目的!