Go | 了解函式作為變數應用
💬 簡介 在 Go 語言中,函式不僅是程式中的基本執行單位,還可以作為變數來使用,這使得函式在應用上更加靈活。 我們可以將函式指派給變數,作為參數傳遞給其他函式,甚至可以讓函式回傳另一個函式。這種特性提供了許多高階應用,例如回呼函式(Callback)、函式組合(Function Composition)與動態行為調整 。
本篇文章將深入探討函式作為變數的應用,並提供範例來展示如何利用這一特性提升程式的彈性與可讀性。
圖片來源:Gophers
🔎 函式作為變數的應用 在 Go 語言中,函式可以被指派給變數,這使得我們能夠動態決定要執行哪個函式。這類似於其他語言中的「一級函式(First-Class Function)」概念。
1️⃣ 將函式指派給變數 在 Go 中,函式可以被儲存在變數中,並透過該變數來調用:
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport "fmt" func sayHello () { fmt.Println("Hello, Go!" ) } func main () { var greet func () // 定義一個函式型別的變數 greet = sayHello greet() }
輸出結果:
📝 在這個範例中,我們先宣告了一個 func() 型別的變數 greet,接著將 sayHello 指派給它,最後透過 greet() 來執行函式。
2️⃣ 直接宣告匿名函式 我們也可以直接使用 匿名函式(Anonymous Function) 來建立函式變數,而不需要額外定義一個函式名稱。
1 2 3 4 5 6 7 8 9 10 11 package mainimport "fmt" func main () { greet := func (name string ) { fmt.Println("Hello," , name) } greet("Alice" ) }
輸出結果:
📝 這種方式常用於簡單的回呼函式(Callback)或作為參數傳遞的函式。
3️⃣ 函式作為參數傳遞 函式可以被當作參數傳遞給其他函式,使我們可以在函式內部調用不同的邏輯。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" func execute (fn func (string ) , name string ) { fn(name) } func main () { sayHello := func (name string ) { fmt.Println("Hello," , name) } execute(sayHello, "Bob" ) }
輸出結果:
📝 execute
函式接收一個 func(string)
型別的函式作為參數,這讓我們能夠傳遞不同的函式來執行。
4️⃣ 函式作為回傳值 除了作為參數傳遞,我們也可以讓函式回傳另一個函式,進一步提升靈活性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func getGreeting () func (string ) string { return func (name string ) string { return "Hello, " + name } } func main () { greet := getGreeting() fmt.Println(greet("Charlie" )) }
輸出結果:
📝 getGreeting
回傳一個匿名函式,該函式可用於產生問候語。
5️⃣ 結合函式變數與高階函式 透過函式變數,我們可以實現函式組合(Function Composition)或動態行為調整。例如,我們可以建立一個計算函式,並傳入不同的運算邏輯:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" func compute (a, b int , op func (int , int ) int ) int { return op(a, b) } func main () { add := func (x, y int ) int { return x + y } multiply := func (x, y int ) int { return x * y } fmt.Println("加法結果:" , compute(10 , 5 , add)) fmt.Println("乘法結果:" , compute(10 , 5 , multiply)) }
輸出結果:
📝 這樣的設計可以讓計算函式更加靈活,根據需求動態決定使用哪種運算方式。
6️⃣ 責任鏈模式應用 責任鏈模式是一種行為設計模式,它允許將請求沿著處理者鏈傳遞,每個處理者可以選擇處理請求或將其傳遞給下一個處理者。在 Go 中,我們可以使用函式變數來動態組合處理流程。
責任鏈模式範例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package mainimport "fmt" type Handler func (string ) string func chainHandlers (handlers ...Handler) Handler { return func (input string ) string { result := input for _, handler := range handlers { result = handler(result) } return result } } func main () { trim := func (s string ) string { return fmt.Sprintf("Trim[%s]" , s) } toUpper := func (s string ) string { return fmt.Sprintf("Upper[%s]" , s) } addPrefix := func (s string ) string { return fmt.Sprintf("Prefix[%s]" , s) } process := chainHandlers(trim, toUpper, addPrefix) result := process("hello golang" ) fmt.Println(result) }
輸出結果:
1 Prefix [Upper[Trim[hello golang] ]]
📝 我們使用 chainHandlers
來接收多個 Handler
,並按照順序依次執行。這種模式可以靈活組合不同的處理流程,例如用於請求過濾、日誌處理、管道處理等場景。
🚀 應用場景 函式變數的應用場景非常廣泛,以下列舉幾個常見應用。
✅ 日誌記錄(Logging) 動態設定不同的日誌級別。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" type Logger func (string ) func main () { infoLogger := func (msg string ) { fmt.Println("[INFO]:" , msg) } errorLogger := func (msg string ) { fmt.Println("[ERROR]:" , msg) } log := infoLogger log("Application started" ) log = errorLogger log("An error occurred" ) }
輸出結果:
1 2 [INFO ]: Application started [ERROR ]: An error occurred
✅ HTTP 請求處理(Middleware) Go 的 HTTP 伺服器可利用函式變數來動態設定請求處理方式,例如加入身份驗證、日誌、錯誤處理等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport ( "fmt" "net/http" ) func loggerMiddleware (next http.HandlerFunc) http .HandlerFunc { return func (w http.ResponseWriter, r *http.Request) { fmt.Println("Request received:" , r.URL.Path) next(w, r) } } func helloHandler (w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello, Go Web!" ) } func main () { http.HandleFunc("/" , loggerMiddleware(helloHandler)) fmt.Println("Server started on :8080" ) http.ListenAndServe(":8080" , nil ) }
當訪問 localhost:8080,終端會輸出:
⚠️ 注意事項 1️⃣ 函式型別必須匹配 當將函式指派給變數或作為參數傳遞時,函式的參數與回傳值型別 必須完全匹配,否則會導致編譯錯誤。
1 2 3 4 5 6 7 8 package mainfunc main () { var fn func (int ) // 函式變數的定義 fn = func (a string ) { println (a) } }
這段程式碼會導致錯誤:
1 2 3 # command-line-arguments .\main.go :4 :6 : declared and not used: fn .\main.go :5 :7 : cannot use func (a string ) {…} (value of type func (a string ) ) as func (int ) value in assignment
2️⃣ 避免未初始化的函式變數 如果函式變數未初始化就直接執行,會導致 nil pointer dereference
錯誤。
1 2 3 4 5 6 package mainfunc main () { var fn func () fn() }
這段程式碼會導致錯誤:
1 2 3 4 5 6 7 panic : runtime error: invalid memory address or nil pointer dereference[signal 0xc0000005 code=0x0 addr=0x0 pc=0x9b9a0c ] goroutine 1 [running]: main.main() D:/workspace/example/main.go :5 +0xc exit status 2
🎯總結
Go 支援將函式作為變數來使用,使得函式可以動態指派、作為參數傳遞或作為回傳值。
使用函式變數可以讓程式更加靈活,例如實現回呼函式、高階函式、動態行為調整等應用。
在使用函式變數時,需要確保函式型別匹配,並避免執行未初始化的函式變數。 透過靈活運用函式變數,我們可以寫出更模組化、彈性強的 Go 程式,提高可維護性與可讀性。
最後建議回顧一下 Go | 菜鳥教學 目錄,了解其章節內容。
註:以上參考了Go