func (c *SomeClass) privateMethod() (result string) {
return “This can only be called in SomeClass”
}
func main() {
// 生成实例
someInstance := SomeClass{
PublicVariable: “hello”,
privateVariable: “world”,
}
}
复制代码
如果你用 Java 来实现上述这个例子,可能会看到冗长的 .java
类文件,例如这样。
// SomeClass.java
public SomeClass {
public String PublicVariable; // 公共变量
private String privateVariable; // 私有变量
// 构造函数
public SomeClass(String val1, String val2) {
this.PublicVariable = val1;
this.privateVariable = val2;
}
// 公共方法
public String PublicMethod() {
return “This can be called by external modules”;
}
// 私有方法
public String privateMethod() {
return “This can only be called in SomeClass”;
}
}
…
// Application.java
public Application {
public static void main(String[] args) {
// 生成实例
SomeClass someInstance = new SomeClass(“hello”, “world”);
}
}
复制代码
可以看到,在 Java 代码中除了容易看花眼的多层花括号以外,还充斥着大量的 public
、private
、static
、this
等修饰用的关键词,显得异常啰嗦;而 Golang 代码中则靠简单的约定,例如首字母大小写,避免了很多重复性的修饰词。当然,Java 和 Go 在类型系统上还是有一些区别的,这也导致 Go 在处理复杂问题显得有些力不从心,这是后话,后面再讨论。总之,结论就是 Go 的语法在静态类型编程语言中非常简洁。
内置并发编程
Go 语言之所以成为分布式应用的首选,除了它性能强大以外,其最主要的原因就是它天然的并发编程。这个并发编程特性主要来自于 Golang 中的协程(Goroutine)和通道(Channel)。下面是使用协程的一个例子。
func asyncTask() {
fmt.Printf(“This is an asynchronized task”)
}
func syncTask() {
fmt.Printf(“This is a synchronized task”)
}
func main() {
go asyncTask() // 异步执行,不阻塞
syncTask() // 同步执行,阻塞
go asyncTask() // 等待前面 syncTask 完成之后,再异步执行,不阻塞
}
复制代码
可以看到,关键词 go
加函数调用可以让其作为一个异步函数执行,不会阻塞后面的代码。而如果不加 go
关键词,则会被当成是同步代码执行。如果读者熟悉 JavaScript 中的 async/await
、Promise
语法,甚至是 Java、Python 中的多线程异步编程,你会发现它们跟 Go 异步编程的简单程度不是一个量级的!
异步函数,也就是协程之间的通信可以用 Go 语言特有的通道来实现。下面是关于通道的一个例子。
func longTask(signal chan int) {
// 不带参数的 for
// 相当于 while 循环
for {
// 接收 signal 通道传值
v := <- signal
// 如果接收值为 1,停止循环
if v == 1 {
break
}
time.Sleep(1 * Second)
}
}
func main() 《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 {
// 声明通道
sig := make(chan int)
// 异步调用 longTask
go longTask(sig)
// 等待 1 秒钟
time.Sleep(1 * time.Second)
// 向通道 sig 传值
sig <- 1
// 然后 longTask 会接收 sig 传值,终止循环
}
复制代码
面向接口编程
Go 语言不是严格的面向对象编程(OOP),它采用的是面向接口编程(IOP),是相对于 OOP 更先进的编程模式。作为 OOP 体系的一部分,IOP 更加强调规则和约束,以及接口类型方法的约定,从而让开发人员尽可能的关注更抽象的程序逻辑,而不是在更细节的实现方式上浪费时间。很多大型项目采用的都是 IOP 的编程模式。如果想了解更多面向接口编程,请查看 “码之道” 个人技术博客的往期文章[《为什么说 TypeScript 是开发大型前端项目的必备语言》]((),其中有关于面向接口编程的详细讲解。
Go 语言跟 TypeScript 一样,也是采用鸭子类型的方式来校验接口继承。下面这个例子可以描述 Go 语言的鸭子类型特性。
// 定义 Animal 接口
interface Animal {
Eat() // 声明 Eat 方法
Move() // 声明 Move 方法
}
// ==== 定义 Dog Start ====
// 定义 Dog 类
type Dog struct {
}
// 实现 Eat 方法
func (d *Dog) Eat() {
fmt.Printf(“Eating bones”)
}
// 实现 Move 方法
func (d *Dog) Move() {
fmt.Printf(“Moving with four legs”)
}
// ==== 定义 Dog End ====
// ==== 定义 Human Start ====
// 定义 Human 类
type Human struct {
}
// 实现 Eat 方法
func (h *Human) Eat() {
fmt.Printf(“Eating rice”)
}
// 实现 Move 方法
func (h *Human) Move() {
fmt.Printf(“Moving with two legs”)
}
// ==== 定义 Human End ====
复制代码
可以看到,虽然 Go 语言可以定义接口,但跟 Java 不同的是,Go 语言中没有显示声明接口实现(Implementation)的关键词修饰语法。在 Go 语言中,如果要继承一个接口,你只需要在结构体中实现该接口声明的所有方法。这样,对于 Go 编译器来说你定义的类就相当于继承了该接口。在这个例子中,我们规定,只要既能吃(Eat)又能活动(Move)的东西就是动物(Animal)。而狗(Dog)和人(Human)恰巧都可以吃和动,因此它们都被算作动物。这种依靠实现方法匹配度的继承方式,就是鸭子类型:如果一个动物看起来像鸭子,叫起来也像鸭子,那它一定是鸭子。这种鸭子类型相对于传统 OOP 编程语言显得更灵活。但是,后面我们会讨论到,这种编程方式会带来一些麻烦。
错误处理
Go 语言的错误处理是臭名昭著的啰嗦。这里先给一个简单例子。
package main
import “fmt”
func isValid(text string) (valid bool, err error){
if text == “” {
return false, error(“text cannot be empty”)
}
return text == “valid text”, nil
}
func validateForm(form map[string]string) (res bool, err error) {
for _, text := range form {
valid, err := isValid(text)
if err != nil {
return false, err
}
if !valid {
return false, nil
}
}
return true, nil
}
func submitForm(form map[string]string) (err error) {
if res, err := validateForm(form); err != nil || !res {
return error(“submit error”)
}
fmt.Printf(“submitted”)
return nil
}
func main() {
form := map[string]string{
“field1”: “”,
“field2”: “invalid text”,
“field2”: “valid text”,
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)