面向对象的三大特征:

  1. 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式
  2. 继承:使得子类具有父类的属性和方法或者重新定义、追加属性和方法等
  3. 多态:不同对象中同种行为的不同实现方式

Go并不是一个纯面向对象的编程语言。在 Go 语言中可以使用结构体struct对属性进行封装,结构体就像是类的一种简化形式。可以在结构体上添加捆绑数据和方法的行为,这些数据和方法与类类似

Go语言没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。

结构体和方法

type Employee struct {
FirstName string
LastName string
TotalLeaves int
LeavesTaken int
} func (e Employee) LeavesRemaining() {
fmt.Printf("%s %s has %d leaves remaining", e.FirstName, e.LastName, (e.TotalLeaves - e.LeavesTaken))
} func main() {
e := employee.Employee {
FirstName: "Sam",
LastName: "Adolf",
TotalLeaves: 30,
LeavesTaken: 20,
}
e.LeavesRemaining()
}

New()函数

Java语言中提供了构造方法创建并初始化对象,在Go语言中一般需要自己实现一个对外可见的New函数

func main() {
var e Employee
e.LeavesRemaining()
}

运行结果:

has 0 leaves remaining

通过运行结果可以知道,使用Employee的零值创建的变量是不可用的。它没有有效的名、姓,也没有有效的保留细节。在其他的OOP语言中,比如java,这个问题可以通过使用构造函数来解决。使用参数化构造函数可以创建一个有效的对象。

go不支持构造函数。如果某个类型的零值不可用,则程序员的任务是不导出该类型以防止其他包的访问,并提供一个名为NewT(parameters)的函数,该函数初始化类型T和所需的值。在go中,它是一个命名一个函数的约定,它创建了一个T类型的值给NewT(parameters)。这就像一个构造函数。如果包只定义了一个类型,那么它的一个约定就是将这个函数命名为New(parameters)而不是NewT(parameters)

首先修改employee结构体为非导出,并创建一个函数New(),它将创建一个新Employee:

type employee struct {
firstName string
lastName string
totalLeaves int
leavesTaken int
} func New(firstName string, lastName string, totalLeave int, leavesTaken int) employee {
e := employee {firstName, lastName, totalLeave, leavesTaken}
return e
} func (e employee) LeavesRemaining() {
fmt.Printf("%s %s has %d leaves remaining", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
}

在这里做了一些重要的改变。已经将Employee struct的起始字母e设置为小写。

由于employee是未导出的,所以不可能从其他包中创建类型employee的值。因此,提供了一个输出的新函数。将所需的参数作为输入并返回新创建的employee

func main() {
e := employee.New("Sam", "Adolf", 30, 20)
e.LeavesRemaining()
}

运行结果:

Sam Adolf has 10 leaves remaining

因此,虽然Go不支持类,但是结构体可以有效地使用,在使用构造函数的位置,使用New(parameters)的方法即可。


组合与继承

在 Go 语言中没有 extends 关键字,它使用在结构体中内嵌匿名类型的方法来实现继承。在Go语言中称之为组合(Composition)。组合的一般定义是“放在一起”。

举一个博客文章例子:每个博客都有标题、内容和作者信息。这可以用组合完美地表示出来。

嵌入结构体实现组合

可以通过将一个struct类型嵌入到另一个结构中实现:

/*
创建了一个author struct,它包含字段名、lastName和bio。添加了一个方法fullName(),将作者作为接收者类型,这将返回作者的全名。
*/
type author struct {
firstName string
lastName string
bio string
} func (a author) fullName() string {
return fmt.Sprintf("%s %s", a.firstName, a.lastName)
}
/*
post struct有字段标题、内容。它还有一个嵌入式匿名字段作者。这个字段表示post struct是由author组成的。现在post struct可以访问作者结构的所有字段和方法。还在post struct中添加了details()方法
*/
type post struct {
title string
content string
author
} func (p post) details() {
fmt.Println("Title: ", p.title)
fmt.Println("Content: ", p.content)
fmt.Println("Author: ", p.author.fullName())
fmt.Println("Bio: ", p.author.bio)
} func main() {
author1 := author{
"Naveen",
"Ramanathan",
"Golang Enthusiast",
}
post1 := post{
"Inheritance in Go",
"Go supports composition instead of inheritance",
author1,
}
post1.details()
}

运行结果:

Title:  Inheritance in Go
Content: Go supports composition instead of inheritance
Author: Naveen Ramanathan
Bio: Golang Enthusiast

嵌入结构体的切片

在以上程序的main函数下增加以下代码,并运行

type website struct {
[]post
}
func (w website) contents() {
fmt.Println("Contents of Website\n")
for _, v := range w.posts {
v.details()
fmt.Println()
}
}

运行报错:

syntax error: unexpected [, expecting field name or embedded type

这个错误指向structs []post的嵌入部分。原因是切片不能当做匿名字段使用。需要一个字段名

type website struct {
posts []post
}

修改完完整代码如下:

type author struct {
firstName string
lastName string
bio string
} func (a author) fullName() string {
return fmt.Sprintf("%s %s", a.firstName, a.lastName)
} type post struct {
title string
content string
author
}
func (p post) details() {
fmt.Println("Title: ", p.title)
fmt.Println("Content: ", p.content)
fmt.Println("Author: ", p.fullName())
fmt.Println("Bio: ", p.bio)
} type website struct {
posts []post
}
func (w website) contents() {
fmt.Println("Contents of Website\n")
for _, v := range w.posts {
v.details()
fmt.Println()
}
}
func main() {
author1 := author{
"Naveen",
"Ramanathan",
"Golang Enthusiast",
}
post1 := post{
"Inheritance in Go",
"Go supports composition instead of inheritance",
author1,
}
post2 := post{
"Struct instead of Classes in Go",
"Go does not support classes but methods can be added to structs",
author1,
}
post3 := post{
"Concurrency",
"Go is a concurrent language and not a parallel one",
author1,
}
w := website{
posts: []post{post1, post2, post3},
}
w.contents()
}

运行结果:

Contents of Website

Title:  Inheritance in Go
Content: Go supports composition instead of inheritance
Author: Naveen Ramanathan
Bio: Golang Enthusiast Title: Struct instead of Classes in Go
Content: Go does not support classes but methods can be added to structs
Author: Naveen Ramanathan
Bio: Golang Enthusiast Title: Concurrency
Content: Go is a concurrent language and not a parallel one
Author: Naveen Ramanathan
Bio: Golang Enthusiast

接口与多态

Go中的多态性(Polymorphism)是在接口的帮助下实现的。接口可以在Go中隐式地实现。如果类型为接口中声明的所有方法提供了定义,则该类型实现了这个接口。

任何定义接口所有方法的类型都被称为隐式地实现该接口。

类型接口的变量可以保存实现接口的任何值。接口的这个属性用于实现Go中的多态性。

定义一个正方形 Square 和一个长方形 Rectangle

// 正方形
type Square struct {
side float32
} // 长方形
type Rectangle struct {
length, width float32
}

计算这两个几何图形的面积。但由于他们的面积计算方式不同,需要定义两个不同的 Area() 方法。

于是,可以定义一个包含 Area() 方法的接口 Shaper,让 SquareRectangle 都实现这个接口里的 Area()

// 接口 Shaper
type Shaper interface {
Area() float32
} // 计算正方形的面积
func (sq *Square) Area() float32 {
return sq.side * sq.side
} // 计算长方形的面积
func (r *Rectangle) Area() float32 {
return r.length * r.width
}

main() 函数中调用 Area()

func main() {
r := &Rectangle{10, 2}
q := &Square{10} // 创建一个 Shaper 类型的数组
shapes := []Shaper{r, q}
// 迭代数组上的每一个元素并调用 Area() 方法
for n, _ := range shapes {
fmt.Println("图形数据: ", shapes[n])
fmt.Println("它的面积是: ", shapes[n].Area())
}
} /*Output:
图形数据: &{10 2}
它的面积是: 20
图形数据: &{10}
它的面积是: 100
*/

由以上代码输出结果可知:不同对象调用 Area() 方法产生了不同的结果,展现了多态的特征。

总结

  • 面向对象的三大特征是:封装、继承和多态
  • Go 语言使用结构体对属性进行封装,结构体就像是类的一种简化形式
  • 在 Go 语言中,方法是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量
  • 名称首字母的大小写决定了该变量/常量/类型/接口/结构/函数……能否被外部包导入
  • 无法被导入的字段可以使用 gettersetter 的方式来访问
  • Go 语言使用在结构体中内嵌匿名类型的方法来实现继承
  • 使用接口可以实现多态

Golang通脉之面向对象的更多相关文章

  1. Golang 中的 面向对象: 方法, 类, 方法继承, 接口, 多态的简单描述与实现

    前言: Golang 相似与C语言, 基础语法与C基本一致,除了广受争议的 左花括号 必须与代码同行的问题, 别的基本差不多; 学会了C, 基本上万变不离其宗, 现在的高级语言身上都能看到C的影子; ...

  2. Golang通脉之方法

    方法和接收者 Go语言中的方法(Method)是一种作用于特定类型变量的函数.这种特定类型变量叫做接收者(Receiver).接收者的概念就类似于其他语言中的this或者 self. Go 语言中同时 ...

  3. [Go] Golang中的面向对象

    struct interface 就可以实现面向对象中的继承,封装,多态 继承的演示:Tsh类型继承People类型,并且使用People类型的方法 多态的演示Tsh类型实现了接口Student,实现 ...

  4. Golang通脉之结构体

    Go语言中的基础数据类型可以表示一些事物的基本属性,但是要表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了,Go语言提供了一种自定义数据类型,可以封装多个基本数据类型, ...

  5. Golang通脉之数据类型

    标识符与关键字 在了解数据类型之前,先了解一下go的标识符和关键字 标识符 在编程语言中标识符就是定义的具有某种意义的词,比如变量名.常量名.函数名等等. Go语言中标识符允许由字母数字和_(下划线) ...

  6. Golang通脉之基础入门

    为什么要学 Go 性能优越感:Go 极其地快,其性能与 Java 或 C++相似.在使用中,Go 一般比 Python 要快 30 倍: 序列化/去序列化.排序和聚合中表现优异: 开发者效率较高:多种 ...

  7. Golang通脉之错误处理

    在实际工程项目中,总是通过程序的错误信息快速定位问题,但是又不希望错误处理代码写的冗余而又啰嗦.Go语言没有提供像Java.C#语言中的try...catch异常处理方式,而是通过函数返回值逐层往上抛 ...

  8. Golang通脉之反射

    什么是反射 官方关于反射定义: Reflection in computing is the ability of a program to examine its own structure, pa ...

  9. Golang通脉之并发初探

    并发是编程里面一个非常重要的概念,Go语言在语言层面天生支持并发. 并发与并行 并发:同一时间段内执行多个任务. 并行:同一时刻执行多个任务,有时间上的重叠. 进程.线程.协程 进程(Process) ...

随机推荐

  1. shell循环语句for

    1.方式1 for i in {list[0]} {list[1]} .. do 执行命令 done 2.方式2(三要素循环) for (( 初始值; 判断值; 步长; )) do 执行命令 done

  2. Django使用tinymce富文本编辑器

    1 - 安装 pip install django-tinymce==2.6.0 2 - 注册app INSTALLED_APPS = ( ... 'tinymce', ) 3 - 在setting中 ...

  3. noip模拟32

    \(\color{white}{\mathbb{山高而青云冷,池深而蛟穴昏,行以慎步,援以轻身,名之以:落石}}\) 开题发现 \(t1\) 80分特别好写,于是先写了 但是这个做法没有任何扩展性,导 ...

  4. Python - 面向对象编程 - self 参数

    为什么要讲 self 参数 class PoloBlog: def __init__(self): ... def say(self): ... 在类里面,所有实例方法都需要加 self 参数,且排在 ...

  5. Pytest 系列(26)- 清空 allure 历史报告记录

    如果你还想从头学起Pytest,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1690628.html 背景 pytest 运行 测试用例 ...

  6. 【Python机器学习实战】决策树与集成学习(六)——集成学习(4)XGBoost原理篇

    XGBoost是陈天奇等人开发的一个开源项目,前文提到XGBoost是GBDT的一种提升和变异形式,其本质上还是一个GBDT,但力争将GBDT的性能发挥到极致,因此这里的X指代的"Extre ...

  7. 清除router路由后面的参数

    清除router参数: 1.this.$router.push({ query: {}}) 2.var path = this.$route.path; //获取路由路径    this.$route ...

  8. 【第六篇】- Maven 仓库之Spring Cloud直播商城 b2b2c电子商务技术总结

    Maven 仓库 在 Maven 的术语中,仓库是一个位置(place). Maven 仓库是项目中依赖的第三方库,这个库所在的位置叫做仓库. 在 Maven 中,任何一个依赖.插件或者项目构建的输出 ...

  9. Centos6.5时间服务器NTP搭建

    NTP时间服务器安装与配置 第1章 Server端的安装与配置 1.1 查看系统是否已经安装ntp服务组件 rpm -qa | grep "ntp" #<==查看是否已经安装 ...

  10. Maven项目之间关系介绍

    Maven项目之间的关系 依赖关系 单纯的项目A中需要项目B中的资源,将项目B打成Jar包被A依赖,此时项目A直接调用项目B中资源即可. 项目A和项目B此时形成最基本的依赖关系. 继承关系 需要场景: ...