接口(interface)初识
接口的定义
这里用一个例子说明了golang中接口的含义与用法,先看代码
// Launch the goroutine to perform the search.
go func(matcher Matcher, feed *Feed) {
Match(matcher, feed, searchTerm, results)
waitGroup.Done()
}(matcher, feed)
上面这段代码是search.go中调用Match函数的代码,其中Match的第一个参数“matcher”就是接口定义如下:
// Matcher defines the behavior required by types that want
// to implement a new search type.
type Matcher interface {
Search(feed *Feed, searchTerm string) ([]*Result, error)
}
通过这个接口,我们的匹配器就可以用一致且通用的方法处理不同类型的匹配值,是不是很优雅
接口的命名
命名接口的时候,也需要遵守Golang的命名惯例。如果接口类型只包含一个方法,那么这个类型的名字以er结尾。我们的例子里就是这么做的,所以这个接口的名字叫作Matcher。如果接口类型内部声明了多个方法,其名字需要与其行为关联。
类实现接口
这里提供一个最简单的这个接口的类的实现
package search
type defaultMatcher struct {
}
func (m defaultMatcher) Search(feed *Feed, searchTerm string) ([]*Result, error) {
return nil, nil
}
上面的代码有两点需要说明:
1.这个代码创建了一个类(defaultMatcher),这是一个空结构体,空结构体创建的时候系统不会分配任何内存,不需要维护状态,只需要实现接口即可,故,空结构体很适合创建没有任何状态的类型。
2.func 后面 Search前面的这个括号的内容是指定接收者,说白了就是把接下来要写的函数绑定在指定的类上,类似Java的成员方法。
接口方法调用受限
因为大部分方法在被调用后都需要维护接收者的值的状态,所以,一个最佳实践是,将方法的接收者声明为指针。对于defaultMatcher类型来说,使用值作为接收者是因为创建一个defaultMatcher类型的值不需要分配内存。由于defaultMatcher不需要维护状态,所以不需要指针形式的接收者。
与直接通过值或者指针调用方法不同,如果通过接口类型的值调用方法,规则有很大不同, 如代码清单2-38所示。使用指针作为接收者声明的方法,只能在接口类型的值是一个指针的时候被调用。使用值作为接收者声明的方法,在接口类型的值为值或者指针时,都可以被调用。
调用实现接口的类的方法
上面我们定义好了接口,根据接口实现了类,现在我们在函数中调用类的方法,用起来~
func Match(matcher Matcher, feed *Feed, searchTerm string, results chan<- *Result) {
// Perform the search against the specified matcher.
searchResults, err := matcher.Search(feed, searchTerm)
if err != nil {
log.Println(err)
return
}
// Write the results to the channel.
for _, result := range searchResults {
results <- result
}
}
这里用到了matcher.Search也就是我们之前接口中定义要求类需要实现的方法。
通道(channel)初识
通道是一种数据结构,可以让goroutine之间进行安全的数据通信。通道可以帮用户避免其他语言里常见的共享内存访问的问题。
并发的最难的部分就是要确保其他并发运行的进程、线程或goroutine不会意外修改用户的数据。当不同的线程在没有同步保护的情况下修改同一个数据时,总会发生灾难。在其他语言中, 如果使用全局变量或者共享内存,必须使用复杂的锁规则来防止对同一个变量的不同步修改。
为了解决这个问题,通道提供了一种新模式,从而保证并发修改时的数据安全。通道这一模式保证同一时刻只会有一个goroutine修改数据。通道用于在几个运行的goroutine之间发送数据。
在图1-3中可以看到数据是如何流动的示例。想象一个应用程序,有多个进程需要顺序读取或者修改某个数据,使用goroutine和通道,可以为这个过程建立安全的模型。
接下来回到我们的程序:
定义通道
// Result contains the result of a search.
type Result struct {
Field string
Content string
}
results chan<- *Result
写入通道
for _, result := range searchResults {
results <- result
}
读出通道
// Display writes results to the console window as they
// are received by the individual goroutines.
func Display(results chan *Result) {
// The channel blocks until a result is written to the channel.
// Once the channel is closed the for loop terminates.
for result := range results {
log.Printf("%s:\n%s\n\n", result.Field, result.Content)
}
}
这里的results通道会一直被阻塞,直到有结果写入通道,一旦通道被关闭,循环就会终止。(这里使用的是无缓冲通道,有无缓冲以后会讲)
参考:Kennedy W , Ketelsen B , Martin E S . Go in action. 2016.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。