帶讀 |《Go in Action》(中文:Go語言實戰)語法和語言結構概覽 (二)

LiberHome發表於2022-12-22

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

相關文章