接口
像Ruby这样的动态语言所强调面向对象编程的风格认为对象的行为比哪种对象是动态类型(duck typing)更为重要。Go所 带来的一个最强大的特性之一就是提供了可以在编程时运用动态类型的思想而把行为定义的合法性检查的工作推到编译时。这一行为的名字被称作接口。
定义一个接口很简单:
type Writer interface {
Write(p []byte) (n int, err os.Error)
}
这里定义了一个接口和一个写字节缓冲的方法。任何实现了这一方法的对象也实现了这一接口。不需要像Java一样进行声明,编译器能推断出来。这既给予了动态类型的表达能力又保留了静态类型检查的安全。
Go当中接口的运作方式支持开发者在编写程序的时候发现程序的类型。如果几个对象间存在公共行为,而开发者想要抽象这种行为,那么它就可以创建一个接口并使用它。
考虑如下的代码:
// Somewhere in some code:
type Widget struct {}
func (Widget) Frob() { /* do something */ }
// Somewhere else in the code:
type Sprocket struct {}
func (Sprocket) Frob() { /* do something else */ }
/* New code, and we want to take both Widgets and Sprockets and Frob them */
type Frobber interface {
Frob()
}
func frobtastic(f Frobber) { f.Frob() }
需要特别指出的很重要的一点就是所有的对象都实现了这个空接口:
interface {}
继承
Go语言不支持继承,至少与大多数语言的继承不一样。并不存在类型的层次结构。相较于继承,Go鼓励使用组合和委派,并为此提供了相应的语法甜点使其更容易接受。
有了这样的定义:
type Engine interface {
Start()
Stop()
}
type Car struct {
Engine
}
于是我可以像下面这样编写:
func GoToWorkIn(c Car) {
/* get in car */
c.Start();
/* drive to work */
c.Stop();
/* get out of car */
}
当我声明Car这个struct的时候,我定义了一个匿名成员。这是一 个只能被其类型识别的成员。匿名成员与其它的成员一样,并有着和类型一样的名字。因此我还可以写成c.Engine.Start()。 如果Car并没有其自身方法可以满足调用的话,编译器自动的会将在Car上的调用委派给它的Engine上面的方法。
由匿名成员提供的分离方法的规则是保守的。如果为一个类型定义了一个方法,就使用它。如果不是,就使用为匿名成员定义的方法。如果有两个匿名成员都提供一 个方法,编译器将会报错,但只在该方法被调用的情况下。
这种组合是通过委派来实现的,而不是继承。一旦匿名成员的方法被调用,控制流整个都被委派给了该方法。所以你无法做到和下面的例子一样来模拟类型层次:
type Base struct {}
func (Base) Magic() { fmt.Print("base magic") }
func (self Base) MoreMagic() {
self.Magic()
self.Magic()
}
type Foo struct {
Base
}
func (Foo) Magic() { fmt.Print("foo magic") }
当你创建一个Foo对象时,它将会影响Base的两个方法。然而,当你调用MoreMagic时, 你将得不到期望的结果:
f := new(Foo)
f.Magic() //=> foo magic
f.MoreMagic() //=> base magic base magic
并发
Go的作者选择了消息传递模型来作为推荐的并发编程方法。该语言同样支持共享内存,然后作者自有道理:
不要通过共享内存来通信,相反,通过通信来共享内存。
该语言提供了两个基本的构件来支持这一范型:goroutines和channels。
Go例程
Goroutine是轻量级的并行程序执行路径,与线程,coroutine或者进程类似。然而,它们彼此相当不同,因此Go作者决定给它一个新的名字并 放弃其它术语可能隐含的意义。
创建一个goroutine来运行名为DoThis的函数十分简单:
go DoThis() // but do not wait for it to complete
匿名的函数可以这样使用:
go func() {
for { /* do something forever */ }
}() // Note that the function must be invoked
这些goroutine将会通过Go运行时而映射到适当的操作系统原语(比如,POSIX线程)。
通道类型
有了goroutine,代码的并行执行就容易了。然而,它们之间仍然需要通讯机制。Channel提供一个FIFO通信队列刚好能达到这一目的。
以下是使用channel的语法:
/* Creating a channel uses make(), not new - it was also used for map creation */
ch := make(chan int)
/* Sending a value blocks until the value is read */
ch <- 4
/* Reading a value blocks until a value is available */
i := <-ch
举例来说,如果我们想要进行长时间运行的数值计算,我们可以这样做:
ch := make(chan int)
go func() {
result := 0
for i := 0; i < 100000000; i++ {
result = result + i
}
ch <- result
}()
/* Do something for a while */
sum := <-ch // This will block if the calculation is not done yet
fmt.Println("The sum is:", sum)
channel的阻塞行为并非永远是最佳的。该语言提供了两种对其进行定制的方式:
- 程序员可以指定缓冲大小??想缓冲的channel发送消息不会阻塞,除非缓冲已满,同样从缓冲的channel读取也不会阻塞,除非缓冲是空的。
- 该语言同时还提供了不会被阻塞的发送和接收的能力,而操作成功是仍然要报告。
/* Create a channel with buffer size 5 */
ch := make(chan int, 5)
/* Send without blocking, ok will be true if value was buffered */
ok := ch <- 42
/* Read without blocking, ok will be true if a value was read */
val, ok := <-ch
包
Go提供了一种简单的机制来组织代码:包。每个文件开头都会声明它属于哪一个包,每个文件也可以引入它所用到的包。任何首字母大写的名字是由包导出的,并可以被其它的包所使用。
以下是一个完整的源文件:
package geometry
import "math"
/* Point is capitalized, so it is visible outside the package. */
type Point struct {
/* the fields are not capitalized, so they are not visible
outside of the package */
x, y float64
}
/* These functions are visible outside of the package */
func (self Point) Length() float64 {
/* This uses a function in the math package */
return math.Sqrt(self.x*self.x + self.y*self.y)
}
func (self *Point) Scale(factor float64) {
self.setX(self.x * factor)
self.setY(self.y * factor)
}
/* These functions are not visible outside of the package, but can be
used inside the package */
func (self *Point) setX(x float64) { self.x = x }
func (self *Point) setY(y float64) { self.y = y }
缺失
Go 语言的作者试图将代码的清晰明确作为设计该语言作出所有决定的指导思想。第二个目标是生产一个编译速度很快的语言。有了这两个标准作为方向,来 自其它语言的许多特性就不那么适合了。许多程序员会发现他们最爱的语言特性在Go当中不存在,确实,有很多人也许会觉得Go语言由于缺乏其它语言所共有的 一些特性,还不太可用。
这当中两个缺失的特性就是异常和泛型,两者在其它语言当中都是非常有用的。而它们目前都不是Go的一分子。但因为该 语言仍处于试验阶段,它们有可能最终会加入到语言里。然而,如果将Go与其它语言作比较的话,我们应当记住Go是打算在系统编程层面作为C语言的替代。明 白这一点的话,那么缺失的这许多特性倒也不是很大的问题了。
最后,因为这一语言才刚刚发布,因此它没有什么类库或工具可以用,也没有Go语 言的集成编程环境。Go语言标准库有些有用的代码,但这与更为成熟的语言比 起来仍还是很少的
查看英文原文:Google Go: A Primer