Go与java、.net这些OOP语言有哪些不同。
1、初识Go
Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。
不太记得是哪一年听说go这门语言了,反正有好多年了,Google出品的即使不学也得了解,因为我对各种编程语言的关注还算是比较感兴趣,当然我也并没有去深入学习,只是稍微了解作为自己的知识广度。
记得在2017年左右,原公司的一个API服务就是用GO来重写的,QPS大概在1-2万左右,算是公司的第一个用GO来写的系统了。那时候全公司有200个系统左右,机器在5千台左右(还没推广一机一应的策略)。公司大部份是.NET、JAVA以及少量的PYTHON(运维部门、DEVOPS)在用。
因为身边有了用GO写的案例,所以让我更加关注这门语言,也因此买了一本纸质书,因为工作忙一直没有静下心来翻阅学习,直到在2022年上海疫情只能留在家里,所以我下定决心开始研究GO。
2、GO能做什么
我们要了解一门语言之前都会有这个疑问:这门语言能做什么
、比起其它语言有什么独特的魅力
、谁在用,生态如何
、性能怎么样
?
可以做:客户端应用、web应用,至于移动端APP、带UI的客户端,这两个我没有接触,在这里不做描述。
还包括像一些分布式应用
(比如ETCD)、网关
(Traefik)、云原生平台的二次开发
(这个招聘岗位不少)区块链
(这个招聘岗位不少)
当然像K8S、Docker这两个就不用多说了。
3、比起其它语言有什么独特的魅力
运行机制:
- go编译出来的是
二进制机器码
的程序。 不需要安装runtime
(.net、java、python需要特定运行环境)- 没有JIT带来首次访问慢的问题,go属于
AOT
。 - go与大部份OOP语言一样,
带GC功能
,我们不需要手动释放内存。 - 编译出来的应用
占用空间非常小
。 - docker可以使用
alpine:latest镜像
代码层面:
语法超级简单
,没有复杂的各种类型。支持interface
,更主要的实现类不需要显示的依赖接口
。- 比多线程更加轻量级的
用户态协程
,一个协程只占用2KB栈空间
。 - 开启一个异步(协程)只需要在调用时,加
go
关键字。 通道channel
,是其它语言没有的先天优势。搭配select使用,超级舒服。- 与大多数OOP语言一样也有
泛型
(可惜不支持方法泛型
,支持结构体泛型
、函数泛型
)。 - 支持
多个返回值
(大多数语言只支持1个以内) - 打开一个go的项目代码看起来像脚本一样轻巧,没有程序集、jar包、子模块的笨重加载过程。
- 不需要maven、nuget,可以
直接引用github的第三方包
- 使用
指针
来控制一个对象使用引用类型、还是值类型。
4、谁在用,生态如何
go的明星产品有很多,像K8s、docker这些大家几乎都会使用到。(我是从traefik开始下定决心来学习GO的)
更主要的是,我从招聘岗位观察,几乎国内的头部公司都在用,并且越来越多。
可惜目前还没有一个非常成熟并且比较权威的框架,这一点java的做的真不错。
另外,转GO方向的,有很多是来自PHP、C++的开发人群。
一款语言值不值得学,看市场、看岗位。这个大家可以先去看下目前go的招聘岗位数量、头部互联网公司的招聘。
5、开发效率、性能如何
从我的使用感受来说(不权威),虽然GO宣传编译很快,应该是对标C++来说,编译速度很快。但是我感觉没有想象中的快,并且还是有点慢。
得益于GO是AOT模式
,没有just in time的那种首次访问慢的问题,这个是我喜欢GO的原因之一,很多时候需要做持续部署,像其他语言的JIT模式导致每次发布后,都要自己去跑一下所有接口的请求。否则首次访问会让下游系统出现超时报警的异常。
虽然go不支持lambda、linq,无法很优雅的实现对一个集合的筛选聚合操作。但是GO在整体开发效率上,我感觉比.NET/JAVA的开发效率要更高些
。如果做一个项目
时让我选择语言的话,我应该会首选GO
。
GO对CPU密集型的操作没有.NET CORE快,这一点等我有时间出一个对比测试。
用GO写出一个异步调用、多线程应用,应该没有其它语言有像GO一样简单了。这也是为什么GO如此优秀,不需要我们掌握多复杂的多线程技术、考虑各种互斥条件,就能写出QPS足够高的应用了。
其他语言的async await是很不错,但会破坏整体的结构(保证所有的下层都要使用async),同时要提供同步版本、异步版本。这在GO中是不存在的,因为要不要使用异步,由调用方决定,不需要实现方设定。
6、GO的Hello World
package main
import "fmt"
func main() {
fmt.Println("Hello World go!")
}
- package:当前包名(或者命名空间)
- import:导入其它包(引用其它实现好的包),这里也可以是直接引用github地址的包
就是让大家有个直观的认识,不做具体的代码讲解。
7、GO的类对象
事实上GO没有class
的,只有结构体:
package serverNode
import (
"github.com/farseer-go/fs"
"github.com/farseer-go/fs/configure"
"github.com/farseer-go/fs/parse"
"strings"
"time"
)
type DomainObject struct {
Id int64 // 客户端ID
Name string // 客户端名称
Ip string // 客户端IP
Port int // 客户端端口
IsLeader bool // 是否为Master
ActivateAt time.Time // 活动时间
}
// SetLeader 设为master
func (receiver *DomainObject) SetLeader(leaderId int64) {
receiver.IsLeader = leaderId == receiver.Id
}
// Activate 更新活跃时间
func (receiver *DomainObject) Activate() {
receiver.ActivateAt = time.Now()
}
import 这里导入了来自github的开源框架
。
DomainObject
是一个结构体ValueType,没有class类型
(要实现class的引用效果,用指针即可)。
SetLeader
、Activate
是DomainObject的方法(Method)
。
我们知道OOP中的类一般有:字段、属性、方法、虚方法、继承。GO在这里只有字段、方法。可以通过匿名字段实现继承的目地。
8、与其他OOP语言的区别
- GO没有类(class),有struct(结构体),可以通过指针来控制引用或值类型。
- GO没有抽象类。
- GO不支持虚方法。
- GO没有属性,只有字段、方法。(OOP一般会有字段、属性、方法)
- GO是通过命名规则来定义public、private的。(大写是public、小写为private)
- GO没有构造函数和析构函数。
- GO没有继承,不过结构体中的匿名字段可以达到同样的效果。
- GO不支持像类、方法的注解、特性,不过GO支持字段的注解。
- GO实例化一个对象时不需要NEW关键词。一般是:结构体名称{}
- GO的struct要实现一个接口时,不需要显示申明,只需要实现这个接口的所有方法即可。
- GO不支持方法重载。
- GO有函数、方法的区分。函数是指没有struct的独立“方法”,而方法就是指struct内部的方法。
- GO的函数可以定义成一个变量类型,做为其它方法、函数的出入参。
- GO的出参支持多个。(在OOP中,多个出参不得不定义一个类出来)
- GO没有方法入参的默认参数设置。
- 方法入参的定义中,变量名称在前、变量类型在后。
- GO的字符串拼接不支持插值方式,一般使用format方式。
- GO没有null的概念(有nil),除了指针、map、切片、channel,都会有默认值。
- GO不支持运算符重载。
- GO没有多线程的概念,但是可以通过GO关键词实现异步(协程)调用。
- GO有一个Channel通道,用于异步调用时的收发消息。
- 其它语言要实现select channel的效果,最起码多出10几行的代码,并且性能远没有GO好。
- 不支持扩展方法。这个其实蛮难受的,不过习惯了也还好。
- GO没有枚举类型。
- GO没有try catch机制。(不过可以自己实现)
- GO无法通过反射查找当前包(程序集)下有哪些定义的struct。
- GO的集合只有数组、切片、Map字典三种类型。
9、有必要学习GO吗
就我使用下来,我觉得还是蛮有必要的,它的语法精简、高效的开发效率、轻巧的开发体验、AOT的编译模式、极小的成本实现并发、带GC、编译的程序体积小、并且很多优秀的中间件也是GO写出来的。
这些优点我想足以吸引你去学习了解这门语言了。
10、学习GO的曲线
GO相对其他语言来说非常简单,因为GO内置的语法非常精简,大概只有25个关键字
左右。如果你有其它编程语言的经验,需要了解这些语法大概只需要几个小时(初步认识这些关键字作用)
我学习的时候习惯看纸质书,大概看了10天左右(期间没有写过一行代码)。在第11天就着手开始写GO的框架。
因为我在其他语言都会有自己的一套框架,所以在看完书入门了之后,就开始用GO来实现框架。
有一点值得注意的是,切片、协程、通道在其它语言上很少见到,所以要真正的理解到位,还是得花上一点时间来研究,毕竟这是一个新大陆,能为你开启新的编程思维。
另外,学习一门新的语言,我们除了掌握基础语法
、熟悉语言的特性外,还得先研究有哪些流行的基础框架
,然后学习各个基础框架的基础用法。比如数据库、redis、es、etcd、eventBus、mapper、队列等等。
在这里,分享一个我开源的框架,实现了我们开发中使用到的大部份基础框架。
11、随处可见的any类型
为什么要使用any类型?any相当于oop中的object类型。
由于go不支持方法泛型
(只支持函数、结构、接口),导致当结构体确定时,要返回不同的“动态”类型时,只能使用any。
// Sum 求总和
func (receiver Enumerable[T]) Sum(fn func(item T) any) any {
lst := *receiver.source
var sum any
for index := 0; index < len(lst); index++ {
sum = Addition(sum, fn(lst[index]))
}
return sum
}
这段代码的入参fn是一个函数变量,fn的返回值,以及Sum方法的返回值都是any类型。
如果支持方法的泛型,这个any就可以是int、float这种数字类型,调用时也不需要再做一层转换了。
12、随处可见的error类型
GO不支持try catch机制,GO推荐使用error机制来返回这个错误类型,由上层判断。
// StringSet 设置缓存
func (redisString *redisString) StringSet(key string, value any) error {
return redisString.rdb.Set(fs.Context, key, value, 0).Err()
}
比如这块的redis操作。StringSet,其间有可能发生网络IO错误,这时是不会抛出异常的,而由上层调用根据error!=nil来判断是否有异常。
13、能使用OOP的IOC、AOP思想吗
对于一些GO开发者来说,一看到这就会吐槽,肯定会说既然用GO,就不要用JAVA那一套了。 难道AOP、IOC = JAVA吗?所以我很不认同这些观点。
对于依赖使用AOP、IOC的小伙伴们,一定是为了将业务与技术实现之间做一个解耦。 举个例子,在一个函数中需要打印日志、埋点、事务,或者开启某个功能。仔细想想这些是不是业务逻辑呢?
习惯了面向对象编程的小伙伴们,肯定会离不开IOC(就像我)。因为我们习惯面向接口编程,就像设计原则中提到的一样。而在GO中使用IOC是完全可以的。
当然原生的标准库中没有IOC框架,不过我自己有实现这个包,感觉兴趣的小伙伴可以在github上看我分享的开源框架。
AOP也是可以支持的,我们知道AOP有两种实现机制:
- 静态植入
- 动态代理
GO在编程的过程中,首先会将我们写的代码转成语法树AST。而这个AST就可以让我们做一个静态植入。 不过目前我还没有花时间去研究这个小领域。
14、select case
通道是其他语言所不具备的原生能力。 channel相当于一个本地队列,可以往这个队列发送消息,同时也可以往这个队列读取消息。通常运用于协程(多线程)。
举个例子: 有一个60S的任务,如果期间有数据需要更新,则先更新数据,然后重新计时60S。
for {
select {
case <- time.After(60 * time.Second).C: // 倒计时60S
// 60S时间到了 doing job
case <-receiver.updated: // 等待数据更新的动作
// 有数据进来,需要先更新
}
}
在GO中实现这个需求,非常简单,利用select case(与switch case有本质区别)。 在这段代码中,更能体现我们人的思维方式。时间倒计时与有数据更新的动作,是两个并行的逻辑,谁先满足就先执行谁。
这里不去解释这些语言的关键字。小伙伴们想想如果用自己擅长的语言,实现起来需要几行代码。(当两个条件还没有满足时,这个时候程序是阻塞状态)
15、最后
本篇不是为了让大家学习GO,只是帮助之前没有接触过GO的同学,有一个大概的了解。
希望对大家有帮助。
转载自:https://juejin.cn/post/7212937418960306233