面向对象的三大特征:

  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. 实例_ Java中的代理模式

    静态代理 我们定义一个接口,并且使用代理模式,想要做到的就是在调用这个接口的实现类时在此方法上添加功能. public interface HelloInterface { void sayHello ...

  2. noip模拟41

    A. 你相信引力吗 很明显的单调栈的题,考场上没有想到平移最大值,而是想着复制一倍序列破环成链,然后发现最大值的部分特别难维护,而且耗费时间过长,只好牺牲时间复杂度加了个 \(map\) 去重. 首先 ...

  3. error : Hooks can only be called inside of the body of a function component. 依赖包和主包加载多个react 引发冲突问题

    1. 结论: 在依赖包和主包的node-modules中,同时install包含react , react-dom 或者react-redux时,跑测试或者启动的时候,显示如下error.当然出现这个 ...

  4. Python - 如何将 list 列表作为数据结构使用

    列表作为栈使用 栈的特点 先进后出,后进先出 如何模拟栈? 先在堆栈尾部添加元素,使用 append() 然后从堆栈顶部取出一个元素,使用 pop() # 模拟栈 stack = [1, 2, 3, ...

  5. fwm环境APP菜品数据加载失败的优化操作

    1)在项目的.env文件中添加如下一行: RESPONSE_CACHE_ENABLED=true 2)拷贝 laravel-worker.conf.example,将laravel字段替换为域名,并执 ...

  6. Object类、Date类、Calendar类、System类、StringBuilder类和基本类型包装类

    一.Object类--toString方法 1.普通类重写toString方法,不然打印出来是存在栈内存的对象引用名称的堆内存中该对象的地址值: 2.equals方法: String比较equals是 ...

  7. shell 脚本 根据PID过滤查看进程所有信息

    #!/bin/bash read -p "输入要查询的PID: " P #筛选第二列等于输入的PID号 n=`ps aux | awk '$2~/^'$P'$/ {print $1 ...

  8. C# 加载Word的3种方法

    本次经验内容分享通过C#程序来加载Word文档的3种不同方法.分别是: 1. 加载本地Word文档 2. 以只读模式加载Word文档 3. 从流加载Word [程序环境] Windows 10 Vis ...

  9. Docker系列(12)- 部署Tomcat

    #官方的使用:我们之前的启动都是后台,停止容器后,容器还是可以看到#docker run -it --rm,一般用来测试,用完就会删除容器,镜像还在[root@localhost ~]# docker ...

  10. Jmeter系列(12)- 上传接口压测

    step-1上传接口分析 上传接口源码分析:分析上传文件类型.有无大小限制.存放上传文件服务器 没有源码通过抓包工具,或者Chrome查看框架源代码 接口路径/uploadfile,接口请求POST, ...