Go?GO!(三) Go的面向对象技术、并发和包的简单介绍

来源:互联网 发布:webpack php loader 编辑:程序博客网 时间:2024/05/18 00:51

Object Orientation 面向对象技术

The Go language supports a style of object-oriented programming similar to that used in C. Data is grouped together into structs, and then functions are defined which operate on those structs. Similar to Python, the language offers a way to define the functions and then call them so that the syntax is not cumbersome.

Go语言支持类似C语言的面向对象技术,数据都是按照结构体的形式进行存储,定义函数去操作这些结构;和python比较类似的是,语言保证了这一套机制不是那么笨重——通过提供定义函数,然后调用他们的方式。

Structs 结构

Declaring a new struct type is simple: 结构体的声明:

type Point struct {  x, y float64}

Values of this type can now be allocated using the built-in function new, which returns a pointer to the value in memory with all slots initialized to the zero value.
可以使用内建的new操作符来分配空间,在go中,new的默认动作是将没有初始化的值置零。

var p *Point = new(Point)p.x = 3p.y = 4

That can get verbose, and one of the goals of the Go language is to be concise whenever possible. So a syntax is provided that both allocates and initializes the struct at the same time:
和上一篇讨论过的Go语言为了避免出现错误,将类型放在后面的作法一致,在声明或者初始化一个结构的时候也遵循该准则:


var p1 Point = Point{3,4}  // Valuevar p2 *Point = &Point{3,4} // Pointer


Methods 方法/成员函数

Once a type has been declared, functions can be declared which take that type as an implicit first parameter:
当一个类型被声明了之后,函数就能将该类型作为第一个隐含的参数传入:


func (self Point) Length() float {  return math.Sqrt(self.x*self.x + self.y*self.y);}


Those functions can then be called as methods on the struct:
由于是隐含传入的,因此,该函数可以使用如下方式调用:


p := Point{3,4}
d := p.Length() //=> 5


Methods can actually be declared on both value and pointer types. Go will handle referencing or dereferencing objects as appropriate, so it is possible to declare methods on both type T and type *T and have them be used as appropriate.
事实上,这些方法的参数既可以是结构本身,也可以是结构的指针。声明函数为T或者T*都是可以的。

Let us extend our Point class with a mutator:

/* Note the receiver is *Point */func (self *Point) Scale(factor float64) {  self.x = self.x * factor  self.y = self.y * factor}

Then we can call it like this:

p.Scale(2);d = p.Length() //=> 10

It is important to understand that the self that is passed in to MoveToXY is a parameter like any other, and parameters are passed by value, not by reference. That is why it must be declared as a pointer type in order to actually change the value. If it were declared as just Point, then the struct that was modified inside the method would not be the same one at the call site - values are copied when they are passed to a function, they are also discarded at the end of it.
理解self和其它参数一样使用传值而非传引用的方式这一点非常重要。这就是需要声明为指针类型的原因啦~如果不声明为指针,那么,self变量在函数里面叫破了喉咙,也不会有人理它的,函数结束了,它也就消失了。

Interfaces 接口

Dynamic languages such as Ruby emphasize a style of object-oriented programming that places more importance on what behavior an object has rather than what type that object is (duck typing). One of the most powerful features that Go brings with it is the ability to program with that duck-typed mentality, and check for adherence to those defined behaviors at compile time. The name given to the behaviors is interfaces.
例如RUBY这样的动态语言在OO技术中,会更多的强调对象的行为而不是对象的类型(了解duck typing 戳我)。Go带给面向对象的优势有二:在精神上高度支持鸭子式的类型,另外,在编译期间也会严格检查这些行为(方法)。这些行为在GO中有了闪亮的名字——接口。

Defining an interface is simple:

type Writer interface {  Write(p []byte) (n int, err os.Error)}

That defines an interface with a method for writing a buffer of bytes. Any object which implements that method also implements the interface. No declarations are required as in Java, the compiler just figures it out. This gives the expressiveness of duck-typing with the safety of static type-checking.
上述的接口定义了一个写入数据到缓冲区中的方法。任何实现该方法的对象同时也需要实现该接口。在java中是不需要声明的,编译器能搞定一切。声明接口使得获取鸭子类型和安全的静态检查得以共存。

The way interfaces behave in Go allows developers to discover their programs' types as they write them. If there are several objects that all have the behavior, and a developer wishes to abstract on that behavior, they can create an interface and then use that.
GO中接口的行为允许程序员在写代码的时候就能够知道这些类型。如果有多中对象都拥有该行为,如果有开发者想要抽象该行为,他们只要创造一个接口,然后使用它就行啦。

Consider the following code:
来看看如下的代码:

// 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() }

上面的函数意味着,只要拥有了Frob方法的类型,都可以使用统一的抽象函数调用。

It is important to note that every object implements the empty interface:
每一个对象都拥有默认的空接口。(有啥用还没有发现)

interface {}

Inheritance 继承

The Go language does not have inheritance, at least not the way most languages do. There is no hierarchy of types. Go encourages the use of composition and delegation over inheritance, and offers some syntactic sugar to make it more bearable.

Given these definitions:

Go并没有继承,至少没有大多数语言所采用的继承方式。在Go的世界中,没有继承类型。Go鼓励使用组合和委托模式,而非使用继承,并且利用语法糖使之具有更好的可用性。

type Engine interface {  Start()  Stop()}type Car struct {  Engine}

I can then write the following:

func GoToWorkIn(c Car) {  /* get in car */  c.Start();  /* drive to work */  c.Stop();  /* get out of car */}

When I declared the Car struct, I gave it what is called an anonymous member. That is a member which is identified only by its type. The anonymous member is a member like any other, with a name the same as the type. So I could have also written c.Engine.Start(). The compiler automatically delegates calls made on Car to methods on itsEngine if the Car does not have methods of its own to satisfy them.

当声明了一个car的结构之后,给它一个不具名的成员。该成员可以由其类型所决定,如果在car的内部也定义了start的方法,那么,不好意思,如果想要使用Engine的方法,需要告诉编译器你的需求。

The rules for resolving methods provided by anonymous members are conservative. If a method is defined for a type, it is used. If not, and a method is defined for an anonymous member that is used. If there are two anonymous members that both provide a method, the compiler will produce an error, but only if that method is called.

解析匿名成员函数的调用过程比较复杂,这么来说吧,如果一个类型定义了该方法,首先使用类型所定义的。否则,如果匿名成员含有该方法,使用匿名成员的方法。如果有两个或者以上的匿名成员都含有该方法,当调用该方法时,编译器会大哭大闹的罢工。

This composition is achieved via delegation, not inheritance. Once the anonymous member's method has been called, flow has been delegated to that method entirely. So you cannot simulate type hierarchy like this:

这种组合其实是通过代理而非继承实现的。一旦匿名成员的方法被调用,那么流程就被该实体的方法所代理了,因此你不能使用如下的方法模拟继承:

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") }

When you create a Foo object, it will respond to both methods that Base does. However, when you call MoreMagic you will not get the results you expect:

f := new(Foo)f.Magic() //=> foo magicf.MoreMagic() //=> base magic base magic
说的简单一点,上面的例子就是说会产生名字的覆盖(名字覆盖的原因参考上文),没有办法实现真正的继承。


Concurrency 并发

The Go authors chose a message-passing model as their recommended method for concurrent programming. The language does still support shared memory, however the authors have the following philosophy:

Do not communicate by sharing memory; instead, share memory by communicating.

The language offers two basic constructs to achieve this paradigm: goroutines and channels.

Go的作者选择了消息传递作为他们所推荐的并发编程模型。Go也支持共享内存,但是作者强烈的表达了其人生哲学:

不要用共享内存通信,取而代之的,使用通信共享内存。

Go语言提供了两种基本的构造方式来达到该哲学:goroutines 和 channels

Goroutines Goroutines保留原始名字

Goroutines are lightweight parallel paths of program execution similar to threads, coroutines, or processes. However, they are sufficiently different from each that the Go authors elected to give them a new name and discard any connotative baggage that the other terms might have.

Goroutines是和线程、协同程序已经进程类似的轻量级并发程序的实现。与上述模型不同的是,blablabla...这行我表示无能为力(不管这词可能会有什么含义,作者们一致投票表决要起一个牛逼闪闪的名字)。

Spawning a goroutine to run a function named DoThis is as simple as this:

go DoThis() // but do not wait for it to complete

Anonymous functions can also be used:

go func() {  for { /* do something forever */ }}() // Note that the function must be invoked

These goroutines are mapped to the appropriate operating-system concurrency primitives (e.g. POSIX threads) by the Go runtime.

Goroutines会被Go runtime自动的映射到对应的并发模型上面(例如POSIX线程)。

Channels KO中国移动的强大IM软件

With goroutines, parallel execution of code is easy. However, a mechanism for communicating between them is still needed. Channels provide a FIFO communication queue that can be used for just this purpose.

有了goroutines之后,并发的实现就很简单了。但是,仍然确保他们之间有一些通讯机制牵线搭桥。这时候,Channels伴随着威武雄壮的”we will rock U“出现了。她提供了一个先进先出的消息队列,该队列能够很好的满足并发的goroutines之间的通讯问题。

Here is the syntax for working with channels:

以下是使用channels的语法:

/* 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

For example, if we wanted to do some long-running numerical computation we could do this:

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 yetfmt.Println("The sum is:", sum)

The blocking behavior of channels is not always the best. The language offers two ways to customize this:

  1. A programmer can specify a buffer size - sending to a buffered channel will not block unless the buffer is full, and reading from a buffered channel will not block unless the buffer is empty
  2. The language also offers the ability to send and receive without ever blocking, while still reporting if the operation succeeded
channels的阻塞机制有时候并不是最好的。Go语言提供了两种优化方法:
  1. 程序员能够制定缓冲区的大小--给一个制定了缓冲的channel发送数据在缓冲区满了之前是不会被阻塞的,当从一个包含了缓冲区的channel中读数据时,只要缓冲区不是空的也不会被阻塞。
  2. Go也能够让发送和接受不适用阻塞的方式,但是仍然会报告操作是否成功。
/* 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

Packages 包

Go offers a simple mechanism for organizing code: packages. Each file begins with a simple declaration of what package it belongs to, and each file can import the packages it uses. Any names which begin with a capital letter are exported from a package, and are available to be used by other packages.

Here is a complete source file:

Go提供了一种简单的机制来组织代码:包。每一个文件都以声明该文件属于哪个包开始,每个文件都能导入其使用的包。首字母大写的所有名字都会被包导出,这些名字(函数,变量,whatever)也能够被其它的包所使用。说的简单一点,只要首字母大写了,那就意味着要导出

也就是说,如果想要写一个类什么的,私有成员只要小写,她就只属于你了。

package geometryimport "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 }
本篇文章至此结束,在下一篇中,将会介绍Effective Go~
原创粉丝点击