Go的研習筆記-day14(以Java的視角學習Go)

原文鏈接:https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/17.4.md

Go 語言模式

  • 逗號 ok 模式
    ,ok,第一個參數是一個值或者nil,第二個參數是true/false或者一個錯誤error。在一個需要賦值的if條件語句中,使用這種模式去檢測第二個參數值會讓代碼顯得優雅簡潔。這種模式在go語言編碼規範中非常重要。下面總結了所有使用這種模式的例子:
  • (1)在函數返回時檢測錯誤
value, err := pack1.Func1(param1)

if err != nil {
    fmt.Printf("Error %s in pack1.Func1 with parameter %v", err.Error(), param1)
    return err
}

// 函數Func1沒有錯誤:
Process(value)

e.g.: os.Open(file) strconv.Atoi(str)

這段代碼中的函數將錯誤返回給它的調用者,當函數執行成功時,返回的錯誤是nil,所以使用這種寫法:
func SomeFunc() error {
    …
    if value, err := pack1.Func1(param1); err != nil {
        …
        return err
    }
    …
    return nil
}
這種模式也常用於通過defer使程序從panic中恢復執行。

要實現簡潔的錯誤檢測代碼,更好的方式是使用閉包
  • 檢測映射中是否存在一個鍵值:key1在映射map1中是否有值?
if value, isPresent = map1[key1]; isPresent {
        Process(value)
}
// key1不存在
…
  • (3)檢測一個接口類型變量varI是否包含了類型T:類型斷言
if value, ok := varI.(T); ok {
    Process(value)
}
// 接口類型varI沒有包含類型T
  • (4)檢測一個通道ch是否關閉
for input := range ch {
        Process(input)
    }
或者:

    for {
        if input, open := <-ch; !open {
            break // 通道是關閉的
        }
        Process(input)
    }
  • defer 模式
    使用 defer 可以確保資源不再需要時,都會被恰當地關閉或歸還到“池子”中。更重要的一點是,它可以恢復 panic。
  • 關閉一個文件流:
    // 先打開一個文件 f
    defer f.Close()
  • 解鎖一個被鎖定的資源(mutex):
    mu.Lock()
    defer mu.Unlock()
  • 關閉一個通道(如有必要):
    ch := make(chan float64)
    defer close(ch)
    也可以是兩個通道:
    answerα, answerβ := make(chan int), make(chan int)
    defer func() { close(answerα); close(answerβ) }()
  • 從 panic 恢復:
    defer func() {
    if err := recover(); err != nil {
    log.Printf(“run time panic: %v”, err)
    }
    }()
  • 停止一個計時器:
    tick1 := time.NewTicker(updateInterval)
    defer tick1.Stop()
  • 釋放一個進程 p:
    p, err := os.StartProcess(…, …, …)
    defer p.Release()
  • 停止 CPU 性能分析並立即寫入:
    pprof.StartCPUProfile(f)
    defer pprof.StopCPUProfile()
    當然 defer 也可以在打印報表時避免忘記輸出頁腳。
  • 可見性模式
    簡單地使用可見性規則控制對類型成員的訪問,他們可以是 Go 變量或函數。
  • 運算符模式和接口
    運算符是一元或二元函數,它返回一個新對象而不修改其參數,類似 C++ 中的 + 和 ,特殊的中綴運算符(+,-, 等)可以被重載以支持類似數學運算的語法。但除了一些特殊情況,Go 語言並不支持運算符重載:爲了克服該限制,運算符必須由函數來模擬。既然 Go 同時支持面向過程和麪向對象編程,我們有兩種選擇:
  • 函數作爲運算符
    運算符由包級別的函數實現,以操作一個或兩個參數,並返回一個新對象。函數針對要操作的對象,在專門的包中實現。例如,假設要在包 matrix 中實現矩陣操作,就會包含 Add() 用於矩陣相加,Mult() 用於矩陣相乘,他們都會返回一個矩陣。這兩個函數通過包名來調用,因此可以創造出如下形式的表達式:
    m := matrix.Add(m1, matrix.Mult(m2, m3))
    如果我們想在這些運算中區分不同類型的矩陣(稀疏或稠密),由於沒有函數重載,我們不得不給函數起不同的名稱,例如:
func addSparseToDense (a *sparseMatrix, b *denseMatrix) *denseMatrix
func addDenseToDense (a *denseMatrix, b *denseMatrix) *denseMatrix
func addSparseToSparse (a *sparseMatrix, b *sparseMatrix) *sparseMatrix

這可不怎麼優雅,我們能選擇的最佳方案是將它們隱藏起來,作爲包的私有函數,並暴露單一的 Add() 函數作爲公共 API。可以在嵌套的 switch 斷言中測試類型,以便在任何支持的參數組合上執行操作:

func Add(a Matrix, b Matrix) Matrix {
	switch a.(type) {
	case sparseMatrix:
		switch b.(type) {
		case sparseMatrix:
			return addSparseToSparse(a.(sparseMatrix), b.(sparseMatrix))
		case denseMatrix:
			return addSparseToDense(a.(sparseMatrix), b.(denseMatrix))
		…
		}
	default:
		// 不支持的參數
		…
	}
}

線性代數包的更詳細信息,可以在 https://github.com/skelterjohn/go.matrix 找到。

  • 方法作爲運算符
    根據接收者類型不同,可以區分不同的方法。因此我們可以爲每種類型簡單地定義 Add 方法,來代替使用多個函數名稱:
func (a *sparseMatrix) Add(b Matrix) Matrix
func (a *denseMatrix) Add(b Matrix) Matrix

每個方法都返回一個新對象,成爲下一個方法調用的接收者,因此我們可以使用鏈式調用表達式:

m := m1.Mult(m2).Add(m3)
比上一節面向過程的形式更簡潔。

正確的實現同樣可以基於類型,通過 switch 類型斷言在運行時確定:

func (a *sparseMatrix) Add(b Matrix) Matrix {
	switch b.(type) {
	case sparseMatrix:
		return addSparseToSparse(a.(sparseMatrix), b.(sparseMatrix))
	case denseMatrix:
		return addSparseToDense(a.(sparseMatrix), b.(denseMatrix))
	…
	default:
		// 不支持的參數
		…
	}
}
  • 使用接口
    當在不同類型上執行相同的方法時,創建一個通用化的接口以實現多態的想法,就會自然產生。
    例如定義一個代數 Algebraic 接口:
type Algebraic interface {
	Add(b Algebraic) Algebraic
	Min(b Algebraic) Algebraic
	Mult(b Algebraic) Algebraic
	…
	Elements()
}
然後爲我們的 matrix 類型定義 Add(),Min(),Mult(),……等方法。

每種實現上述 Algebraic 接口類型的方法都可以鏈式調用。
每個方法實現都應基於參數類型,使用 switch 類型斷言來提供優化過的實現。
另外,應該爲僅依賴於接口的方法,指定一個默認處理分支:
func (a *denseMatrix) Add(b Algebraic) Algebraic {
	switch b.(type) {
	case sparseMatrix:
		return addDenseToSparse(a, b.(sparseMatrix))
	…
	default:
		for x in range b.Elements() …
	}
}

如果一個通用的功能無法僅使用接口方法來實現,你可能正在處理兩個不怎麼相似的類型,此時應該放棄這種運算符模式。例如,如果 a 是一個集合而 b 是一個矩陣,那麼編寫 a.Add(b) 沒有意義。就集合和矩陣運算而言,很難實現一個通用的 a.Add(b) 方法。遇到這種情況,把包拆分成兩個,然後提供單獨的 AlgebraicSet 和 AlgebraicMatrix 接口。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章