介面(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.